From c9008152c56e0dd441dbc31207267bd0aff07e0f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Dec 2023 17:26:08 +0000
Subject: [PATCH] Migrate widgets/* from Cypress to Playwright (#12032)
* Migrate send_event.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate read_events.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate kick.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate get-openid-token.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate layout.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate events.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate stickers.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Migrate widget-pip-close.spec.ts from Cypress to Playwright
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Fix types
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Add screenshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* expect.poll to stabilise test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
.../get-openid-token.spec.ts | 141 ---------
cypress/e2e/integration-manager/kick.spec.ts | 265 -----------------
.../integration-manager/read_events.spec.ts | 276 ------------------
.../integration-manager/send_event.spec.ts | 261 -----------------
cypress/e2e/widgets/events.spec.ts | 200 -------------
cypress/e2e/widgets/layout.spec.ts | 132 ---------
cypress/e2e/widgets/widget-pip-close.spec.ts | 207 -------------
.../get-openid-token.spec.ts | 128 ++++++++
.../e2e/integration-manager/kick.spec.ts | 226 ++++++++++++++
.../integration-manager/read_events.spec.ts | 233 +++++++++++++++
.../integration-manager/send_event.spec.ts | 255 ++++++++++++++++
playwright/e2e/integration-manager/utils.ts | 25 ++
playwright/e2e/widgets/events.spec.ts | 176 +++++++++++
playwright/e2e/widgets/layout.spec.ts | 119 ++++++++
.../e2e/widgets/stickers.spec.ts | 136 ++++-----
.../e2e/widgets/widget-pip-close.spec.ts | 169 +++++++++++
playwright/global.d.ts | 3 +
playwright/pages/client.ts | 48 +++
playwright/plugins/webserver/index.ts | 2 +-
.../layout.spec.ts/apps-drawer-linux.png | Bin 0 -> 6617 bytes
20 files changed, 1439 insertions(+), 1563 deletions(-)
delete mode 100644 cypress/e2e/integration-manager/get-openid-token.spec.ts
delete mode 100644 cypress/e2e/integration-manager/kick.spec.ts
delete mode 100644 cypress/e2e/integration-manager/read_events.spec.ts
delete mode 100644 cypress/e2e/integration-manager/send_event.spec.ts
delete mode 100644 cypress/e2e/widgets/events.spec.ts
delete mode 100644 cypress/e2e/widgets/layout.spec.ts
delete mode 100644 cypress/e2e/widgets/widget-pip-close.spec.ts
create mode 100644 playwright/e2e/integration-manager/get-openid-token.spec.ts
create mode 100644 playwright/e2e/integration-manager/kick.spec.ts
create mode 100644 playwright/e2e/integration-manager/read_events.spec.ts
create mode 100644 playwright/e2e/integration-manager/send_event.spec.ts
create mode 100644 playwright/e2e/integration-manager/utils.ts
create mode 100644 playwright/e2e/widgets/events.spec.ts
create mode 100644 playwright/e2e/widgets/layout.spec.ts
rename {cypress => playwright}/e2e/widgets/stickers.spec.ts (52%)
create mode 100644 playwright/e2e/widgets/widget-pip-close.spec.ts
create mode 100644 playwright/snapshots/widgets/layout.spec.ts/apps-drawer-linux.png
diff --git a/cypress/e2e/integration-manager/get-openid-token.spec.ts b/cypress/e2e/integration-manager/get-openid-token.spec.ts
deleted file mode 100644
index b2dcb9146a..0000000000
--- a/cypress/e2e/integration-manager/get-openid-token.spec.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { UserCredentials } from "../../support/login";
-
-const ROOM_NAME = "Integration Manager Test";
-const USER_DISPLAY_NAME = "Alice";
-
-const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
-const INTEGRATION_MANAGER_HTML = `
-
-
- Fake Integration Manager
-
-
-
-
- No response
-
-
-
-`;
-
-function openIntegrationManager() {
- cy.findByRole("button", { name: "Room info" }).click();
- cy.findByRole("button", { name: "Add widgets, bridges & bots" }).click();
-}
-
-function sendActionFromIntegrationManager(integrationManagerUrl: string) {
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.findByRole("button", { name: "Press to send action" }).should("exist").click();
- });
-}
-
-describe("Integration Manager: Get OpenID Token", () => {
- let testUser: UserCredentials;
- let homeserver: HomeserverInstance;
- let integrationManagerUrl: string;
-
- beforeEach(() => {
- cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
- integrationManagerUrl = url;
- });
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
- cy.window().then((win) => {
- win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
- win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
- });
- }).then((user) => {
- testUser = user;
- });
-
- cy.setAccountData("m.widgets", {
- "m.integration_manager": {
- content: {
- type: "m.integration_manager",
- name: "Integration Manager",
- url: integrationManagerUrl,
- data: {
- api_url: integrationManagerUrl,
- },
- },
- id: "integration-manager",
- },
- }).as("integrationManager");
-
- // Succeed when checking the token is valid
- cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => {
- req.continue((res) => {
- return res.send(200, {
- user_id: testUser.userId,
- });
- });
- });
-
- cy.createRoom({
- name: ROOM_NAME,
- }).as("roomId");
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- cy.stopWebServers();
- });
-
- it("should successfully obtain an openID token", () => {
- cy.all([cy.get<{}>("@integrationManager")]).then(() => {
- cy.viewRoomByName(ROOM_NAME);
-
- openIntegrationManager();
- sendActionFromIntegrationManager(integrationManagerUrl);
-
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response").within(() => {
- cy.findByText(/access_token/);
- });
- });
- });
- });
-});
diff --git a/cypress/e2e/integration-manager/kick.spec.ts b/cypress/e2e/integration-manager/kick.spec.ts
deleted file mode 100644
index 7075c1c199..0000000000
--- a/cypress/e2e/integration-manager/kick.spec.ts
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { MatrixClient } from "../../global";
-import { UserCredentials } from "../../support/login";
-
-const ROOM_NAME = "Integration Manager Test";
-const USER_DISPLAY_NAME = "Alice";
-const BOT_DISPLAY_NAME = "Bob";
-const KICK_REASON = "Goodbye";
-
-const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
-const INTEGRATION_MANAGER_HTML = `
-
-
- Fake Integration Manager
-
-
-
-
-
-
-
-
-
-`;
-
-function openIntegrationManager() {
- cy.findByRole("button", { name: "Room info" }).click();
- cy.findByRole("button", { name: "Add widgets, bridges & bots" }).click();
-}
-
-function closeIntegrationManager(integrationManagerUrl: string) {
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.findByRole("button", { name: "Press to close" }).should("exist").click();
- });
-}
-
-function sendActionFromIntegrationManager(integrationManagerUrl: string, targetRoomId: string, targetUserId: string) {
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#target-room-id").should("exist").type(targetRoomId);
- cy.get("#target-user-id").should("exist").type(targetUserId);
- cy.findByRole("button", { name: "Press to send action" }).should("exist").click();
- });
-}
-
-function clickUntilGone(selector: string, attempt = 0) {
- if (attempt === 11) {
- throw new Error("clickUntilGone attempt count exceeded");
- }
-
- cy.get(selector)
- .last()
- .click()
- .then(($button) => {
- const exists = Cypress.$(selector).length > 0;
- if (exists) {
- clickUntilGone(selector, ++attempt);
- }
- });
-}
-
-function expectKickedMessage(shouldExist: boolean) {
- // Expand any event summaries, we can't use a click multiple here because clicking one might de-render others
- // This is quite horrible but seems the most stable way of clicking 0-N buttons,
- // one at a time with a full re-evaluation after each click
- clickUntilGone(".mx_GenericEventListSummary_toggle[aria-expanded=false]");
-
- // Check for the event message (or lack thereof)
- cy.findByText(`${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`).should(
- shouldExist ? "exist" : "not.exist",
- );
-}
-
-describe("Integration Manager: Kick", () => {
- let testUser: UserCredentials;
- let homeserver: HomeserverInstance;
- let integrationManagerUrl: string;
-
- beforeEach(() => {
- cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
- integrationManagerUrl = url;
- });
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
- cy.window().then((win) => {
- win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
- win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
- });
- }).then((user) => {
- testUser = user;
- });
-
- cy.setAccountData("m.widgets", {
- "m.integration_manager": {
- content: {
- type: "m.integration_manager",
- name: "Integration Manager",
- url: integrationManagerUrl,
- data: {
- api_url: integrationManagerUrl,
- },
- },
- id: "integration-manager",
- },
- }).as("integrationManager");
-
- // Succeed when checking the token is valid
- cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => {
- req.continue((res) => {
- return res.send(200, {
- user_id: testUser.userId,
- });
- });
- });
-
- cy.createRoom({
- name: ROOM_NAME,
- }).as("roomId");
-
- cy.getBot(homeserver, { displayName: BOT_DISPLAY_NAME, autoAcceptInvites: true }).as("bob");
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- cy.stopWebServers();
- });
-
- it("should kick the target", () => {
- cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(
- ([targetUser, roomId]) => {
- const targetUserId = targetUser.getUserId();
- cy.viewRoomByName(ROOM_NAME);
- cy.inviteUser(roomId, targetUserId);
- cy.findByText(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
-
- openIntegrationManager();
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
- closeIntegrationManager(integrationManagerUrl);
- expectKickedMessage(true);
- },
- );
- });
-
- it("should not kick the target if lacking permissions", () => {
- cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(
- ([targetUser, roomId]) => {
- const targetUserId = targetUser.getUserId();
- cy.viewRoomByName(ROOM_NAME);
- cy.inviteUser(roomId, targetUserId);
- cy.findByText(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
- cy.getClient()
- .then(async (client) => {
- await client.sendStateEvent(roomId, "m.room.power_levels", {
- kick: 50,
- users: {
- [testUser.userId]: 0,
- },
- });
- })
- .then(() => {
- openIntegrationManager();
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
- closeIntegrationManager(integrationManagerUrl);
- expectKickedMessage(false);
- });
- },
- );
- });
-
- it("should no-op if the target already left", () => {
- cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(
- ([targetUser, roomId]) => {
- const targetUserId = targetUser.getUserId();
- cy.viewRoomByName(ROOM_NAME);
- cy.inviteUser(roomId, targetUserId);
- cy.findByText(`${BOT_DISPLAY_NAME} joined the room`)
- .should("exist")
- .then(async () => {
- await targetUser.leave(roomId);
- })
- .then(() => {
- openIntegrationManager();
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
- closeIntegrationManager(integrationManagerUrl);
- expectKickedMessage(false);
- });
- },
- );
- });
-
- it("should no-op if the target was banned", () => {
- cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(
- ([targetUser, roomId]) => {
- const targetUserId = targetUser.getUserId();
- cy.viewRoomByName(ROOM_NAME);
- cy.inviteUser(roomId, targetUserId);
- cy.findByText(`${BOT_DISPLAY_NAME} joined the room`).should("exist");
- cy.getClient()
- .then(async (client) => {
- await client.ban(roomId, targetUserId);
- })
- .then(() => {
- openIntegrationManager();
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
- closeIntegrationManager(integrationManagerUrl);
- expectKickedMessage(false);
- });
- },
- );
- });
-
- it("should no-op if the target was never a room member", () => {
- cy.all([cy.get("@bob"), cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(
- ([targetUser, roomId]) => {
- const targetUserId = targetUser.getUserId();
- cy.viewRoomByName(ROOM_NAME);
-
- openIntegrationManager();
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
- closeIntegrationManager(integrationManagerUrl);
- expectKickedMessage(false);
- },
- );
- });
-});
diff --git a/cypress/e2e/integration-manager/read_events.spec.ts b/cypress/e2e/integration-manager/read_events.spec.ts
deleted file mode 100644
index 65b195a3c7..0000000000
--- a/cypress/e2e/integration-manager/read_events.spec.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { UserCredentials } from "../../support/login";
-
-const ROOM_NAME = "Integration Manager Test";
-const USER_DISPLAY_NAME = "Alice";
-
-const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
-const INTEGRATION_MANAGER_HTML = `
-
-
- Fake Integration Manager
-
-
-
-
-
-
-
- No response
-
-
-
-`;
-
-function openIntegrationManager() {
- cy.findByRole("button", { name: "Room info" }).click();
- cy.get(".mx_RoomSummaryCard_appsGroup").within(() => {
- cy.findByRole("button", { name: "Add widgets, bridges & bots" }).click();
- });
-}
-
-function sendActionFromIntegrationManager(
- integrationManagerUrl: string,
- targetRoomId: string,
- eventType: string,
- stateKey: string | boolean,
-) {
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#target-room-id").should("exist").type(targetRoomId);
- cy.get("#event-type").should("exist").type(eventType);
- cy.get("#state-key").should("exist").type(JSON.stringify(stateKey));
- cy.get("#send-action").should("exist").click();
- });
-}
-
-describe("Integration Manager: Read Events", () => {
- let testUser: UserCredentials;
- let homeserver: HomeserverInstance;
- let integrationManagerUrl: string;
-
- beforeEach(() => {
- cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
- integrationManagerUrl = url;
- });
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
- cy.window().then((win) => {
- win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
- win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
- });
- }).then((user) => {
- testUser = user;
- });
-
- cy.setAccountData("m.widgets", {
- "m.integration_manager": {
- content: {
- type: "m.integration_manager",
- name: "Integration Manager",
- url: integrationManagerUrl,
- data: {
- api_url: integrationManagerUrl,
- },
- },
- id: "integration-manager",
- },
- }).as("integrationManager");
-
- // Succeed when checking the token is valid
- cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => {
- req.continue((res) => {
- return res.send(200, {
- user_id: testUser.userId,
- });
- });
- });
-
- cy.createRoom({
- name: ROOM_NAME,
- }).as("roomId");
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- cy.stopWebServers();
- });
-
- it("should read a state event by state key", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- const eventType = "io.element.integrations.installations";
- const eventContent = {
- foo: "bar",
- };
- const stateKey = "state-key-123";
-
- // Send a state event
- cy.getClient()
- .then(async (client) => {
- return await client.sendStateEvent(roomId, eventType, eventContent, stateKey);
- })
- .then((event) => {
- openIntegrationManager();
-
- // Read state events
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, eventType, stateKey);
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response")
- .should("include.text", event.event_id)
- .should("include.text", `"content":${JSON.stringify(eventContent)}`);
- });
- });
- });
- });
-
- it("should read a state event with empty state key", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- const eventType = "io.element.integrations.installations";
- const eventContent = {
- foo: "bar",
- };
- const stateKey = "";
-
- // Send a state event
- cy.getClient()
- .then(async (client) => {
- return await client.sendStateEvent(roomId, eventType, eventContent, stateKey);
- })
- .then((event) => {
- openIntegrationManager();
-
- // Read state events
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, eventType, stateKey);
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response")
- .should("include.text", event.event_id)
- .should("include.text", `"content":${JSON.stringify(eventContent)}`);
- });
- });
- });
- });
-
- it("should read state events with any state key", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- const eventType = "io.element.integrations.installations";
-
- const stateKey1 = "state-key-123";
- const eventContent1 = {
- foo1: "bar1",
- };
- const stateKey2 = "state-key-456";
- const eventContent2 = {
- foo2: "bar2",
- };
- const stateKey3 = "state-key-789";
- const eventContent3 = {
- foo3: "bar3",
- };
-
- // Send state events
- cy.getClient()
- .then(async (client) => {
- return Promise.all([
- client.sendStateEvent(roomId, eventType, eventContent1, stateKey1),
- client.sendStateEvent(roomId, eventType, eventContent2, stateKey2),
- client.sendStateEvent(roomId, eventType, eventContent3, stateKey3),
- ]);
- })
- .then((events) => {
- openIntegrationManager();
-
- // Read state events
- sendActionFromIntegrationManager(
- integrationManagerUrl,
- roomId,
- eventType,
- true, // Any state key
- );
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response")
- .should("include.text", events[0].event_id)
- .should("include.text", `"content":${JSON.stringify(eventContent1)}`)
- .should("include.text", events[1].event_id)
- .should("include.text", `"content":${JSON.stringify(eventContent2)}`)
- .should("include.text", events[2].event_id)
- .should("include.text", `"content":${JSON.stringify(eventContent3)}`);
- });
- });
- });
- });
-
- it("should fail to read an event type which is not allowed", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- const eventType = "com.example.event";
- const stateKey = "";
-
- openIntegrationManager();
-
- // Read state events
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, eventType, stateKey);
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response").should("include.text", "Failed to read events");
- });
- });
- });
-});
diff --git a/cypress/e2e/integration-manager/send_event.spec.ts b/cypress/e2e/integration-manager/send_event.spec.ts
deleted file mode 100644
index d8a746b423..0000000000
--- a/cypress/e2e/integration-manager/send_event.spec.ts
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { UserCredentials } from "../../support/login";
-
-const ROOM_NAME = "Integration Manager Test";
-const USER_DISPLAY_NAME = "Alice";
-
-const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
-const INTEGRATION_MANAGER_HTML = `
-
-
- Fake Integration Manager
-
-
-
-
-
-
-
-
- No response
-
-
-
-`;
-
-function openIntegrationManager() {
- cy.findByRole("button", { name: "Room info" }).click();
- cy.get(".mx_RoomSummaryCard_appsGroup").within(() => {
- cy.findByRole("button", { name: "Add widgets, bridges & bots" }).click();
- });
-}
-
-function sendActionFromIntegrationManager(
- integrationManagerUrl: string,
- targetRoomId: string,
- eventType: string,
- stateKey: string,
- content: Record,
-) {
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#target-room-id").should("exist").type(targetRoomId);
- cy.get("#event-type").should("exist").type(eventType);
- if (stateKey) {
- cy.get("#state-key").should("exist").type(stateKey);
- }
- cy.get("#event-content").should("exist").type(JSON.stringify(content), { parseSpecialCharSequences: false });
- cy.get("#send-action").should("exist").click();
- });
-}
-
-describe("Integration Manager: Send Event", () => {
- let testUser: UserCredentials;
- let homeserver: HomeserverInstance;
- let integrationManagerUrl: string;
-
- beforeEach(() => {
- cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
- integrationManagerUrl = url;
- });
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
- cy.window().then((win) => {
- win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
- win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
- });
- }).then((user) => {
- testUser = user;
- });
-
- cy.setAccountData("m.widgets", {
- "m.integration_manager": {
- content: {
- type: "m.integration_manager",
- name: "Integration Manager",
- url: integrationManagerUrl,
- data: {
- api_url: integrationManagerUrl,
- },
- },
- id: "integration-manager",
- },
- }).as("integrationManager");
-
- // Succeed when checking the token is valid
- cy.intercept(`${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`, (req) => {
- req.continue((res) => {
- return res.send(200, {
- user_id: testUser.userId,
- });
- });
- });
-
- cy.createRoom({
- name: ROOM_NAME,
- }).as("roomId");
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- cy.stopWebServers();
- });
-
- it("should send a state event", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- openIntegrationManager();
-
- const eventType = "io.element.integrations.installations";
- const eventContent = {
- foo: "bar",
- };
- const stateKey = "state-key-123";
-
- // Send the event
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, eventType, stateKey, eventContent);
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response").should("include.text", "event_id");
- });
-
- // Check the event
- cy.getClient()
- .then(async (client) => {
- return await client.getStateEvent(roomId, eventType, stateKey);
- })
- .then((event) => {
- expect(event).to.deep.equal(eventContent);
- });
- });
- });
-
- it("should send a state event with empty content", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- openIntegrationManager();
-
- const eventType = "io.element.integrations.installations";
- const eventContent = {};
- const stateKey = "state-key-123";
-
- // Send the event
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, eventType, stateKey, eventContent);
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response").should("include.text", "event_id");
- });
-
- // Check the event
- cy.getClient()
- .then(async (client) => {
- return await client.getStateEvent(roomId, eventType, stateKey);
- })
- .then((event) => {
- expect(event).to.be.empty;
- });
- });
- });
-
- it("should send a state event with empty state key", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- openIntegrationManager();
-
- const eventType = "io.element.integrations.installations";
- const eventContent = {
- foo: "bar",
- };
- const stateKey = "";
-
- // Send the event
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, eventType, stateKey, eventContent);
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response").should("include.text", "event_id");
- });
-
- // Check the event
- cy.getClient()
- .then(async (client) => {
- return await client.getStateEvent(roomId, eventType, stateKey);
- })
- .then((event) => {
- expect(event).to.deep.equal(eventContent);
- });
- });
- });
-
- it("should fail to send an event type which is not allowed", () => {
- cy.all([cy.get("@roomId"), cy.get<{}>("@integrationManager")]).then(([roomId]) => {
- cy.viewRoomByName(ROOM_NAME);
-
- openIntegrationManager();
-
- const eventType = "com.example.event";
- const eventContent = {
- foo: "bar",
- };
- const stateKey = "";
-
- // Send the event
- sendActionFromIntegrationManager(integrationManagerUrl, roomId, eventType, stateKey, eventContent);
-
- // Check the response
- cy.accessIframe(`iframe[src*="${integrationManagerUrl}"]`).within(() => {
- cy.get("#message-response").should("include.text", "Failed to send event");
- });
- });
- });
-});
diff --git a/cypress/e2e/widgets/events.spec.ts b/cypress/e2e/widgets/events.spec.ts
deleted file mode 100644
index 58e4c09679..0000000000
--- a/cypress/e2e/widgets/events.spec.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
-Copyright 2022 Mikhail Aheichyk
-Copyright 2022 Nordeck IT + Consulting GmbH.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { IWidget } from "matrix-widget-api/src/interfaces/IWidget";
-
-import type { MatrixClient } from "matrix-js-sdk/src/matrix";
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { UserCredentials } from "../../support/login";
-import { waitForRoom } from "../utils";
-
-const DEMO_WIDGET_ID = "demo-widget-id";
-const DEMO_WIDGET_NAME = "Demo Widget";
-const DEMO_WIDGET_TYPE = "demo";
-const ROOM_NAME = "Demo";
-
-const DEMO_WIDGET_HTML = `
-
-
- Demo Widget
-
-
-
-
-
-
-`;
-
-describe("Widget Events", () => {
- let homeserver: HomeserverInstance;
- let user: UserCredentials;
- let bot: MatrixClient;
- let demoWidgetUrl: string;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, "Mike").then((_user) => {
- user = _user;
- });
- cy.getBot(homeserver, { displayName: "Bot", autoAcceptInvites: true }).then((_bot) => {
- bot = _bot;
- });
- });
- cy.serveHtmlFile(DEMO_WIDGET_HTML).then((url) => {
- demoWidgetUrl = url;
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- cy.stopWebServers();
- });
-
- it("should be updated if user is re-invited into the room with updated state event", () => {
- cy.createRoom({
- name: ROOM_NAME,
- invite: [bot.getUserId()],
- }).then((roomId) => {
- // setup widget via state event
- cy.getClient()
- .then(async (matrixClient) => {
- const content: IWidget = {
- id: DEMO_WIDGET_ID,
- creatorUserId: "somebody",
- type: DEMO_WIDGET_TYPE,
- name: DEMO_WIDGET_NAME,
- url: demoWidgetUrl,
- };
- await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, DEMO_WIDGET_ID);
- })
- .as("widgetEventSent");
-
- // set initial layout
- cy.getClient()
- .then(async (matrixClient) => {
- const content = {
- widgets: {
- [DEMO_WIDGET_ID]: {
- container: "top",
- index: 1,
- width: 100,
- height: 0,
- },
- },
- };
- await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, "");
- })
- .as("layoutEventSent");
-
- // open the room
- cy.viewRoomByName(ROOM_NAME);
-
- // approve capabilities
- cy.get(".mx_WidgetCapabilitiesPromptDialog").within(() => {
- cy.findByRole("button", { name: "Approve" }).click();
- });
-
- cy.all([cy.get("@widgetEventSent"), cy.get("@layoutEventSent")]).then(async () => {
- // bot creates a new room with 'm.room.topic'
- const { room_id: roomNew } = await bot.createRoom({
- name: "New room",
- initial_state: [
- {
- type: "m.room.topic",
- state_key: "",
- content: {
- topic: "topic initial",
- },
- },
- ],
- });
-
- await bot.invite(roomNew, user.userId);
-
- // widget should receive 'm.room.topic' event after invite
- cy.window().then(async (win) => {
- await waitForRoom(win, win.mxMatrixClientPeg.get(), roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "net.widget_echo" &&
- e.getContent().type === "m.room.topic" &&
- e.getContent().content.topic === "topic initial",
- );
- });
- });
-
- // update the topic
- await bot.sendStateEvent(
- roomNew,
- "m.room.topic",
- {
- topic: "topic updated",
- },
- "",
- );
-
- await bot.invite(roomNew, user.userId, "something changed in the room");
-
- // widget should receive updated 'm.room.topic' event after re-invite
- cy.window().then(async (win) => {
- await waitForRoom(win, win.mxMatrixClientPeg.get(), roomId, (room) => {
- const events = room.getLiveTimeline().getEvents();
- return events.some(
- (e) =>
- e.getType() === "net.widget_echo" &&
- e.getContent().type === "m.room.topic" &&
- e.getContent().content.topic === "topic updated",
- );
- });
- });
- });
- });
- });
-});
diff --git a/cypress/e2e/widgets/layout.spec.ts b/cypress/e2e/widgets/layout.spec.ts
deleted file mode 100644
index 16470fd5a0..0000000000
--- a/cypress/e2e/widgets/layout.spec.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
-Copyright 2022 Oliver Sand
-Copyright 2022 Nordeck IT + Consulting GmbH.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { IWidget } from "matrix-widget-api";
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-
-const ROOM_NAME = "Test Room";
-const WIDGET_ID = "fake-widget";
-const WIDGET_HTML = `
-
-
- Fake Widget
-
-
- Hello World
-
-
-`;
-
-describe("Widget Layout", () => {
- let widgetUrl: string;
- let homeserver: HomeserverInstance;
- let roomId: string;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, "Sally");
- });
- cy.serveHtmlFile(WIDGET_HTML).then((url) => {
- widgetUrl = url;
- });
-
- cy.createRoom({
- name: ROOM_NAME,
- }).then((id) => {
- roomId = id;
-
- // setup widget via state event
- cy.getClient()
- .then(async (matrixClient) => {
- const content: IWidget = {
- id: WIDGET_ID,
- creatorUserId: "somebody",
- type: "widget",
- name: "widget",
- url: widgetUrl,
- };
- await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, WIDGET_ID);
- })
- .as("widgetEventSent");
-
- // set initial layout
- cy.getClient()
- .then(async (matrixClient) => {
- const content = {
- widgets: {
- [WIDGET_ID]: {
- container: "top",
- index: 1,
- width: 100,
- height: 0,
- },
- },
- };
- await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, "");
- })
- .as("layoutEventSent");
- });
-
- cy.all([cy.get("@widgetEventSent"), cy.get("@layoutEventSent")]).then(() => {
- // open the room
- cy.viewRoomByName(ROOM_NAME);
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- cy.stopWebServers();
- });
-
- it("should be set properly", () => {
- cy.get(".mx_AppsDrawer").percySnapshotElement("Widgets drawer on the timeline (AppsDrawer)");
- });
-
- it("manually resize the height of the top container layout", () => {
- cy.get('iframe[title="widget"]').invoke("height").should("be.lessThan", 250);
-
- cy.get(".mx_AppsDrawer_resizer_container_handle")
- .trigger("mousedown")
- .trigger("mousemove", { clientX: 0, clientY: 550, force: true })
- .trigger("mouseup", { clientX: 0, clientY: 550, force: true });
-
- cy.get('iframe[title="widget"]').invoke("height").should("be.greaterThan", 400);
- });
-
- it("programatically resize the height of the top container layout", () => {
- cy.get('iframe[title="widget"]').invoke("height").should("be.lessThan", 250);
-
- cy.getClient().then(async (matrixClient) => {
- const content = {
- widgets: {
- [WIDGET_ID]: {
- container: "top",
- index: 1,
- width: 100,
- height: 100,
- },
- },
- };
- await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, "");
- });
-
- cy.get('iframe[title="widget"]').invoke("height").should("be.greaterThan", 400);
- });
-});
diff --git a/cypress/e2e/widgets/widget-pip-close.spec.ts b/cypress/e2e/widgets/widget-pip-close.spec.ts
deleted file mode 100644
index ca717947d0..0000000000
--- a/cypress/e2e/widgets/widget-pip-close.spec.ts
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
-Copyright 2022 Mikhail Aheichyk
-Copyright 2022 Nordeck IT + Consulting GmbH.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-///
-
-import { IWidget } from "matrix-widget-api/src/interfaces/IWidget";
-
-import type { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { UserCredentials } from "../../support/login";
-
-const DEMO_WIDGET_ID = "demo-widget-id";
-const DEMO_WIDGET_NAME = "Demo Widget";
-const DEMO_WIDGET_TYPE = "demo";
-const ROOM_NAME = "Demo";
-
-const DEMO_WIDGET_HTML = `
-
-
- Demo Widget
-
-
-
-
-
-
-`;
-
-// mostly copied from src/utils/WidgetUtils.waitForRoomWidget with small modifications
-function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: string, add: boolean): Promise {
- const matrixClient = win.mxMatrixClientPeg.get();
-
- return new Promise((resolve, reject) => {
- function eventsInIntendedState(evList) {
- const widgetPresent = evList.some((ev) => {
- return ev.getContent() && ev.getContent()["id"] === widgetId;
- });
- if (add) {
- return widgetPresent;
- } else {
- return !widgetPresent;
- }
- }
-
- const room = matrixClient.getRoom(roomId);
-
- const startingWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
- if (eventsInIntendedState(startingWidgetEvents)) {
- resolve();
- return;
- }
-
- function onRoomStateEvents(ev: MatrixEvent) {
- if (ev.getRoomId() !== roomId || ev.getType() !== "im.vector.modular.widgets") return;
-
- const currentWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
-
- if (eventsInIntendedState(currentWidgetEvents)) {
- matrixClient.removeListener(win.matrixcs.RoomStateEvent.Events, onRoomStateEvents);
- resolve();
- }
- }
-
- matrixClient.on(win.matrixcs.RoomStateEvent.Events, onRoomStateEvents);
- });
-}
-
-describe("Widget PIP", () => {
- let homeserver: HomeserverInstance;
- let user: UserCredentials;
- let bot: MatrixClient;
- let demoWidgetUrl: string;
-
- function roomCreateAddWidgetPip(userRemove: "leave" | "kick" | "ban") {
- cy.createRoom({
- name: ROOM_NAME,
- invite: [bot.getUserId()],
- }).then((roomId) => {
- // sets bot to Admin and user to Moderator
- cy.getClient()
- .then((matrixClient) => {
- return matrixClient.sendStateEvent(roomId, "m.room.power_levels", {
- users: {
- [user.userId]: 50,
- [bot.getUserId()]: 100,
- },
- });
- })
- .as("powerLevelsChanged");
-
- // bot joins the room
- cy.botJoinRoom(bot, roomId).as("botJoined");
-
- // setup widget via state event
- cy.getClient()
- .then(async (matrixClient) => {
- const content: IWidget = {
- id: DEMO_WIDGET_ID,
- creatorUserId: "somebody",
- type: DEMO_WIDGET_TYPE,
- name: DEMO_WIDGET_NAME,
- url: demoWidgetUrl,
- };
- await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, DEMO_WIDGET_ID);
- })
- .as("widgetEventSent");
-
- // open the room
- cy.viewRoomByName(ROOM_NAME);
-
- cy.all([
- cy.get("@powerLevelsChanged"),
- cy.get("@botJoined"),
- cy.get("@widgetEventSent"),
- ]).then(() => {
- cy.window().then(async (win) => {
- // wait for widget state event
- await waitForRoomWidget(win, DEMO_WIDGET_ID, roomId, true);
-
- // activate widget in pip mode
- win.mxActiveWidgetStore.setWidgetPersistence(DEMO_WIDGET_ID, roomId, true);
-
- // checks that pip window is opened
- cy.get(".mx_WidgetPip").should("exist");
-
- // checks that widget is opened in pip
- cy.accessIframe(`iframe[title="${DEMO_WIDGET_NAME}"]`).within({}, () => {
- cy.get("#demo")
- .should("exist")
- .then(async () => {
- const userId = user.userId;
- if (userRemove == "leave") {
- cy.getClient().then(async (matrixClient) => {
- await matrixClient.leave(roomId);
- });
- } else if (userRemove == "kick") {
- await bot.kick(roomId, userId);
- } else if (userRemove == "ban") {
- await bot.ban(roomId, userId);
- }
-
- // checks that pip window is closed
- cy.get(".mx_WidgetPip").should("not.exist");
- });
- });
- });
- });
- });
- }
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, "Mike").then((_user) => {
- user = _user;
- });
- cy.getBot(homeserver, { displayName: "Bot", autoAcceptInvites: false }).then((_bot) => {
- bot = _bot;
- });
- });
- cy.serveHtmlFile(DEMO_WIDGET_HTML).then((url) => {
- demoWidgetUrl = url;
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- cy.stopWebServers();
- });
-
- it("should be closed on leave", () => {
- roomCreateAddWidgetPip("leave");
- });
-
- it("should be closed on kick", () => {
- roomCreateAddWidgetPip("kick");
- });
-
- it("should be closed on ban", () => {
- roomCreateAddWidgetPip("ban");
- });
-});
diff --git a/playwright/e2e/integration-manager/get-openid-token.spec.ts b/playwright/e2e/integration-manager/get-openid-token.spec.ts
new file mode 100644
index 0000000000..c107bb2cbc
--- /dev/null
+++ b/playwright/e2e/integration-manager/get-openid-token.spec.ts
@@ -0,0 +1,128 @@
+/*
+Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import type { Page } from "@playwright/test";
+import { test, expect } from "../../element-web-test";
+import { openIntegrationManager } from "./utils";
+
+const ROOM_NAME = "Integration Manager Test";
+
+const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
+const INTEGRATION_MANAGER_HTML = `
+
+
+ Fake Integration Manager
+
+
+
+
+ No response
+
+
+
+`;
+
+async function sendActionFromIntegrationManager(page: Page, integrationManagerUrl: string) {
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await iframe.getByRole("button", { name: "Press to send action" }).click();
+}
+
+test.describe("Integration Manager: Get OpenID Token", () => {
+ test.use({
+ displayName: "Alice",
+ room: async ({ user, app }, use) => {
+ const roomId = await app.client.createRoom({
+ name: ROOM_NAME,
+ });
+ await use({ roomId });
+ },
+ });
+
+ let integrationManagerUrl: string;
+ test.beforeEach(async ({ page, webserver }) => {
+ integrationManagerUrl = webserver.start(INTEGRATION_MANAGER_HTML);
+
+ await page.addInitScript(
+ ({ token, integrationManagerUrl }) => {
+ window.localStorage.setItem("mx_scalar_token", token);
+ window.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, token);
+ },
+ {
+ token: INTEGRATION_MANAGER_TOKEN,
+ integrationManagerUrl,
+ },
+ );
+ });
+
+ test.beforeEach(async ({ page, user, app, room }) => {
+ await app.client.setAccountData("m.widgets", {
+ "m.integration_manager": {
+ content: {
+ type: "m.integration_manager",
+ name: "Integration Manager",
+ url: integrationManagerUrl,
+ data: {
+ api_url: integrationManagerUrl,
+ },
+ },
+ id: "integration-manager",
+ },
+ });
+
+ // Succeed when checking the token is valid
+ await page.route(
+ `${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`,
+ async (route) => {
+ await route.fulfill({
+ json: {
+ user_id: user.userId,
+ },
+ });
+ },
+ );
+
+ await app.viewRoomByName(ROOM_NAME);
+ });
+
+ test("should successfully obtain an openID token", async ({ page }) => {
+ await openIntegrationManager(page);
+ await sendActionFromIntegrationManager(page, integrationManagerUrl);
+
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response").getByText(/access_token/)).toBeVisible();
+ });
+});
diff --git a/playwright/e2e/integration-manager/kick.spec.ts b/playwright/e2e/integration-manager/kick.spec.ts
new file mode 100644
index 0000000000..b5ca6a1b3a
--- /dev/null
+++ b/playwright/e2e/integration-manager/kick.spec.ts
@@ -0,0 +1,226 @@
+/*
+Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import type { Page } from "@playwright/test";
+import { test, expect } from "../../element-web-test";
+import { openIntegrationManager } from "./utils";
+
+const ROOM_NAME = "Integration Manager Test";
+const USER_DISPLAY_NAME = "Alice";
+const BOT_DISPLAY_NAME = "Bob";
+const KICK_REASON = "Goodbye";
+
+const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
+const INTEGRATION_MANAGER_HTML = `
+
+
+ Fake Integration Manager
+
+
+
+
+
+
+
+
+
+`;
+
+async function closeIntegrationManager(page: Page, integrationManagerUrl: string) {
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await iframe.getByRole("button", { name: "Press to close" }).click();
+}
+
+async function sendActionFromIntegrationManager(
+ page: Page,
+ integrationManagerUrl: string,
+ targetRoomId: string,
+ targetUserId: string,
+) {
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await iframe.locator("#target-room-id").fill(targetRoomId);
+ await iframe.locator("#target-user-id").fill(targetUserId);
+ await iframe.getByRole("button", { name: "Press to send action" }).click();
+}
+
+async function clickUntilGone(page: Page, selector: string, attempt = 0) {
+ if (attempt === 11) {
+ throw new Error("clickUntilGone attempt count exceeded");
+ }
+
+ await page.locator(selector).last().click();
+
+ const count = await page.locator(selector).count();
+ if (count > 0) {
+ return clickUntilGone(page, selector, ++attempt);
+ }
+}
+
+async function expectKickedMessage(page: Page, shouldExist: boolean) {
+ // Expand any event summaries, we can't use a click multiple here because clicking one might de-render others
+ // This is quite horrible but seems the most stable way of clicking 0-N buttons,
+ // one at a time with a full re-evaluation after each click
+ await clickUntilGone(page, ".mx_GenericEventListSummary_toggle[aria-expanded=false]");
+
+ // Check for the event message (or lack thereof)
+ await expect(page.getByText(`${USER_DISPLAY_NAME} removed ${BOT_DISPLAY_NAME}: ${KICK_REASON}`)).toBeVisible({
+ visible: shouldExist,
+ });
+}
+
+test.describe("Integration Manager: Kick", () => {
+ test.use({
+ displayName: "Alice",
+ room: async ({ user, app }, use) => {
+ const roomId = await app.client.createRoom({
+ name: ROOM_NAME,
+ });
+ await use({ roomId });
+ },
+ botCreateOpts: {
+ displayName: BOT_DISPLAY_NAME,
+ autoAcceptInvites: true,
+ },
+ });
+
+ let integrationManagerUrl: string;
+ test.beforeEach(async ({ page, webserver }) => {
+ integrationManagerUrl = webserver.start(INTEGRATION_MANAGER_HTML);
+
+ await page.addInitScript(
+ ({ token, integrationManagerUrl }) => {
+ window.localStorage.setItem("mx_scalar_token", token);
+ window.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, token);
+ },
+ {
+ token: INTEGRATION_MANAGER_TOKEN,
+ integrationManagerUrl,
+ },
+ );
+ });
+
+ test.beforeEach(async ({ page, user, app, room }) => {
+ await app.client.setAccountData("m.widgets", {
+ "m.integration_manager": {
+ content: {
+ type: "m.integration_manager",
+ name: "Integration Manager",
+ url: integrationManagerUrl,
+ data: {
+ api_url: integrationManagerUrl,
+ },
+ },
+ id: "integration-manager",
+ },
+ });
+
+ // Succeed when checking the token is valid
+ await page.route(
+ `${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`,
+ async (route) => {
+ await route.fulfill({
+ json: {
+ user_id: user.userId,
+ },
+ });
+ },
+ );
+
+ await app.viewRoomByName(ROOM_NAME);
+ });
+
+ test("should kick the target", async ({ page, app, bot: targetUser, room }) => {
+ await app.viewRoomByName(ROOM_NAME);
+ await app.client.inviteUser(room.roomId, targetUser.credentials.userId);
+ await expect(page.getByText(`${BOT_DISPLAY_NAME} joined the room`)).toBeVisible();
+
+ await openIntegrationManager(page);
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId);
+ await closeIntegrationManager(page, integrationManagerUrl);
+ await expectKickedMessage(page, true);
+ });
+
+ test("should not kick the target if lacking permissions", async ({ page, app, user, bot: targetUser, room }) => {
+ await app.viewRoomByName(ROOM_NAME);
+ await app.client.inviteUser(room.roomId, targetUser.credentials.userId);
+ await expect(page.getByText(`${BOT_DISPLAY_NAME} joined the room`)).toBeVisible();
+
+ await app.client.sendStateEvent(room.roomId, "m.room.power_levels", {
+ kick: 50,
+ users: {
+ [user.userId]: 0,
+ },
+ });
+
+ await openIntegrationManager(page);
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId);
+ await closeIntegrationManager(page, integrationManagerUrl);
+ await expectKickedMessage(page, false);
+ });
+
+ test("should no-op if the target already left", async ({ page, app, bot: targetUser, room }) => {
+ await app.viewRoomByName(ROOM_NAME);
+ await app.client.inviteUser(room.roomId, targetUser.credentials.userId);
+ await expect(page.getByText(`${BOT_DISPLAY_NAME} joined the room`)).toBeVisible();
+ await targetUser.leave(room.roomId);
+
+ await openIntegrationManager(page);
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId);
+ await closeIntegrationManager(page, integrationManagerUrl);
+ await expectKickedMessage(page, false);
+ });
+
+ test("should no-op if the target was banned", async ({ page, app, bot: targetUser, room }) => {
+ await app.viewRoomByName(ROOM_NAME);
+ await app.client.inviteUser(room.roomId, targetUser.credentials.userId);
+ await expect(page.getByText(`${BOT_DISPLAY_NAME} joined the room`)).toBeVisible();
+ await app.client.ban(room.roomId, targetUser.credentials.userId);
+
+ await openIntegrationManager(page);
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId);
+ await closeIntegrationManager(page, integrationManagerUrl);
+ await expectKickedMessage(page, false);
+ });
+
+ test("should no-op if the target was never a room member", async ({ page, app, bot: targetUser, room }) => {
+ await app.viewRoomByName(ROOM_NAME);
+
+ await openIntegrationManager(page);
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, targetUser.credentials.userId);
+ await closeIntegrationManager(page, integrationManagerUrl);
+ await expectKickedMessage(page, false);
+ });
+});
diff --git a/playwright/e2e/integration-manager/read_events.spec.ts b/playwright/e2e/integration-manager/read_events.spec.ts
new file mode 100644
index 0000000000..b178596674
--- /dev/null
+++ b/playwright/e2e/integration-manager/read_events.spec.ts
@@ -0,0 +1,233 @@
+/*
+Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import type { Page } from "@playwright/test";
+import { test, expect } from "../../element-web-test";
+import { openIntegrationManager } from "./utils";
+
+const ROOM_NAME = "Integration Manager Test";
+
+const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
+const INTEGRATION_MANAGER_HTML = `
+
+
+ Fake Integration Manager
+
+
+
+
+
+
+
+ No response
+
+
+
+`;
+
+async function sendActionFromIntegrationManager(
+ page: Page,
+ integrationManagerUrl: string,
+ targetRoomId: string,
+ eventType: string,
+ stateKey: string | boolean,
+) {
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await iframe.locator("#target-room-id").fill(targetRoomId);
+ await iframe.locator("#event-type").fill(eventType);
+ await iframe.locator("#state-key").fill(JSON.stringify(stateKey));
+ await iframe.locator("#send-action").click();
+}
+
+test.describe("Integration Manager: Read Events", () => {
+ test.use({
+ displayName: "Alice",
+ room: async ({ user, app }, use) => {
+ const roomId = await app.client.createRoom({
+ name: ROOM_NAME,
+ });
+ await use({ roomId });
+ },
+ });
+
+ let integrationManagerUrl: string;
+ test.beforeEach(async ({ page, webserver }) => {
+ integrationManagerUrl = webserver.start(INTEGRATION_MANAGER_HTML);
+
+ await page.addInitScript(
+ ({ token, integrationManagerUrl }) => {
+ window.localStorage.setItem("mx_scalar_token", token);
+ window.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, token);
+ },
+ {
+ token: INTEGRATION_MANAGER_TOKEN,
+ integrationManagerUrl,
+ },
+ );
+ });
+
+ test.beforeEach(async ({ page, user, app, room }) => {
+ await app.client.setAccountData("m.widgets", {
+ "m.integration_manager": {
+ content: {
+ type: "m.integration_manager",
+ name: "Integration Manager",
+ url: integrationManagerUrl,
+ data: {
+ api_url: integrationManagerUrl,
+ },
+ },
+ id: "integration-manager",
+ },
+ });
+
+ // Succeed when checking the token is valid
+ await page.route(
+ `${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`,
+ async (route) => {
+ await route.fulfill({
+ json: {
+ user_id: user.userId,
+ },
+ });
+ },
+ );
+
+ await app.viewRoomByName(ROOM_NAME);
+ });
+
+ test("should read a state event by state key", async ({ page, app, room }) => {
+ const eventType = "io.element.integrations.installations";
+ const eventContent = {
+ foo: "bar",
+ };
+ const stateKey = "state-key-123";
+
+ // Send a state event
+ const sendEventResponse = await app.client.sendStateEvent(room.roomId, eventType, eventContent, stateKey);
+ await openIntegrationManager(page);
+
+ // Read state events
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, eventType, stateKey);
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText(sendEventResponse.event_id);
+ await expect(iframe.locator("#message-response")).toContainText(`"content":${JSON.stringify(eventContent)}`);
+ });
+
+ test("should read a state event with empty state key", async ({ page, app, room }) => {
+ const eventType = "io.element.integrations.installations";
+ const eventContent = {
+ foo: "bar",
+ };
+ const stateKey = "";
+
+ // Send a state event
+ const sendEventResponse = await app.client.sendStateEvent(room.roomId, eventType, eventContent, stateKey);
+ await openIntegrationManager(page);
+
+ // Read state events
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, eventType, stateKey);
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText(sendEventResponse.event_id);
+ await expect(iframe.locator("#message-response")).toContainText(`"content":${JSON.stringify(eventContent)}`);
+ });
+
+ test("should read state events with any state key", async ({ page, app, room }) => {
+ const eventType = "io.element.integrations.installations";
+
+ const stateKey1 = "state-key-123";
+ const eventContent1 = {
+ foo1: "bar1",
+ };
+ const stateKey2 = "state-key-456";
+ const eventContent2 = {
+ foo2: "bar2",
+ };
+ const stateKey3 = "state-key-789";
+ const eventContent3 = {
+ foo3: "bar3",
+ };
+
+ // Send state events
+ const sendEventResponses = await Promise.all([
+ app.client.sendStateEvent(room.roomId, eventType, eventContent1, stateKey1),
+ app.client.sendStateEvent(room.roomId, eventType, eventContent2, stateKey2),
+ app.client.sendStateEvent(room.roomId, eventType, eventContent3, stateKey3),
+ ]);
+
+ await openIntegrationManager(page);
+
+ // Read state events
+ await sendActionFromIntegrationManager(
+ page,
+ integrationManagerUrl,
+ room.roomId,
+ eventType,
+ true, // Any state key
+ );
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText(sendEventResponses[0].event_id);
+ await expect(iframe.locator("#message-response")).toContainText(`"content":${JSON.stringify(eventContent1)}`);
+ await expect(iframe.locator("#message-response")).toContainText(sendEventResponses[1].event_id);
+ await expect(iframe.locator("#message-response")).toContainText(`"content":${JSON.stringify(eventContent2)}`);
+ await expect(iframe.locator("#message-response")).toContainText(sendEventResponses[2].event_id);
+ await expect(iframe.locator("#message-response")).toContainText(`"content":${JSON.stringify(eventContent3)}`);
+ });
+
+ test("should fail to read an event type which is not allowed", async ({ page, room }) => {
+ const eventType = "com.example.event";
+ const stateKey = "";
+
+ await openIntegrationManager(page);
+
+ // Read state events
+ await sendActionFromIntegrationManager(page, integrationManagerUrl, room.roomId, eventType, stateKey);
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText("Failed to read events");
+ });
+});
diff --git a/playwright/e2e/integration-manager/send_event.spec.ts b/playwright/e2e/integration-manager/send_event.spec.ts
new file mode 100644
index 0000000000..61bad8a3ec
--- /dev/null
+++ b/playwright/e2e/integration-manager/send_event.spec.ts
@@ -0,0 +1,255 @@
+/*
+Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import type { Page } from "@playwright/test";
+import { test, expect } from "../../element-web-test";
+import { openIntegrationManager } from "./utils";
+
+const ROOM_NAME = "Integration Manager Test";
+
+const INTEGRATION_MANAGER_TOKEN = "DefinitelySecret_DoNotUseThisForReal";
+const INTEGRATION_MANAGER_HTML = `
+
+
+ Fake Integration Manager
+
+
+
+
+
+
+
+
+ No response
+
+
+
+`;
+
+async function sendActionFromIntegrationManager(
+ page: Page,
+ integrationManagerUrl: string,
+ targetRoomId: string,
+ eventType: string,
+ stateKey: string,
+ content: Record,
+) {
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await iframe.locator("#target-room-id").fill(targetRoomId);
+ await iframe.locator("#event-type").fill(eventType);
+ if (stateKey) {
+ await iframe.locator("#state-key").fill(stateKey);
+ }
+ await iframe.locator("#event-content").fill(JSON.stringify(content));
+ await iframe.locator("#send-action").click();
+}
+
+test.describe("Integration Manager: Send Event", () => {
+ test.use({
+ displayName: "Alice",
+ room: async ({ user, app }, use) => {
+ const roomId = await app.client.createRoom({
+ name: ROOM_NAME,
+ });
+ await use({ roomId });
+ },
+ });
+
+ let integrationManagerUrl: string;
+ test.beforeEach(async ({ page, webserver }) => {
+ integrationManagerUrl = webserver.start(INTEGRATION_MANAGER_HTML);
+
+ await page.addInitScript(
+ ({ token, integrationManagerUrl }) => {
+ window.localStorage.setItem("mx_scalar_token", token);
+ window.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, token);
+ },
+ {
+ token: INTEGRATION_MANAGER_TOKEN,
+ integrationManagerUrl,
+ },
+ );
+ });
+
+ test.beforeEach(async ({ page, user, app, room }) => {
+ await app.client.setAccountData("m.widgets", {
+ "m.integration_manager": {
+ content: {
+ type: "m.integration_manager",
+ name: "Integration Manager",
+ url: integrationManagerUrl,
+ data: {
+ api_url: integrationManagerUrl,
+ },
+ },
+ id: "integration-manager",
+ },
+ });
+
+ // Succeed when checking the token is valid
+ await page.route(
+ `${integrationManagerUrl}/account?scalar_token=${INTEGRATION_MANAGER_TOKEN}*`,
+ async (route) => {
+ await route.fulfill({
+ json: {
+ user_id: user.userId,
+ },
+ });
+ },
+ );
+
+ await app.viewRoomByName(ROOM_NAME);
+ await openIntegrationManager(page);
+ });
+
+ test("should send a state event", async ({ page, app, room }) => {
+ const eventType = "io.element.integrations.installations";
+ const eventContent = {
+ foo: "bar",
+ };
+ const stateKey = "state-key-123";
+
+ // Send the event
+ await sendActionFromIntegrationManager(
+ page,
+ integrationManagerUrl,
+ room.roomId,
+ eventType,
+ stateKey,
+ eventContent,
+ );
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText("event_id");
+
+ // Check the event
+ const event = await app.client.evaluate(
+ (cli, { room, eventType, stateKey }) => {
+ return cli.getStateEvent(room.roomId, eventType, stateKey);
+ },
+ { room, eventType, stateKey },
+ );
+ expect(event).toMatchObject(eventContent);
+ });
+
+ test("should send a state event with empty content", async ({ page, app, room }) => {
+ const eventType = "io.element.integrations.installations";
+ const eventContent = {};
+ const stateKey = "state-key-123";
+
+ // Send the event
+ await sendActionFromIntegrationManager(
+ page,
+ integrationManagerUrl,
+ room.roomId,
+ eventType,
+ stateKey,
+ eventContent,
+ );
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText("event_id");
+
+ // Check the event
+ const event = await app.client.evaluate(
+ (cli, { room, eventType, stateKey }) => {
+ return cli.getStateEvent(room.roomId, eventType, stateKey);
+ },
+ { room, eventType, stateKey },
+ );
+ expect(event).toMatchObject({});
+ });
+
+ test("should send a state event with empty state key", async ({ page, app, room }) => {
+ const eventType = "io.element.integrations.installations";
+ const eventContent = {
+ foo: "bar",
+ };
+ const stateKey = "";
+
+ // Send the event
+ await sendActionFromIntegrationManager(
+ page,
+ integrationManagerUrl,
+ room.roomId,
+ eventType,
+ stateKey,
+ eventContent,
+ );
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText("event_id");
+
+ // Check the event
+ const event = await app.client.evaluate(
+ (cli, { room, eventType, stateKey }) => {
+ return cli.getStateEvent(room.roomId, eventType, stateKey);
+ },
+ { room, eventType, stateKey },
+ );
+ expect(event).toMatchObject(eventContent);
+ });
+
+ test("should fail to send an event type which is not allowed", async ({ page, room }) => {
+ const eventType = "com.example.event";
+ const eventContent = {
+ foo: "bar",
+ };
+ const stateKey = "";
+
+ // Send the event
+ await sendActionFromIntegrationManager(
+ page,
+ integrationManagerUrl,
+ room.roomId,
+ eventType,
+ stateKey,
+ eventContent,
+ );
+
+ // Check the response
+ const iframe = page.frameLocator(`iframe[src*="${integrationManagerUrl}"]`);
+ await expect(iframe.locator("#message-response")).toContainText("Failed to send event");
+ });
+});
diff --git a/playwright/e2e/integration-manager/utils.ts b/playwright/e2e/integration-manager/utils.ts
new file mode 100644
index 0000000000..259ff732c7
--- /dev/null
+++ b/playwright/e2e/integration-manager/utils.ts
@@ -0,0 +1,25 @@
+/*
+Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import type { Page } from "@playwright/test";
+
+export async function openIntegrationManager(page: Page) {
+ await page.getByRole("button", { name: "Room info" }).click();
+ await page
+ .locator(".mx_RoomSummaryCard_appsGroup")
+ .getByRole("button", { name: "Add widgets, bridges & bots" })
+ .click();
+}
diff --git a/playwright/e2e/widgets/events.spec.ts b/playwright/e2e/widgets/events.spec.ts
new file mode 100644
index 0000000000..a336bd2cfa
--- /dev/null
+++ b/playwright/e2e/widgets/events.spec.ts
@@ -0,0 +1,176 @@
+/*
+Copyright 2022 Mikhail Aheichyk
+Copyright 2022 Nordeck IT + Consulting GmbH.
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { test } from "../../element-web-test";
+import { waitForRoom } from "../utils";
+
+const DEMO_WIDGET_ID = "demo-widget-id";
+const DEMO_WIDGET_NAME = "Demo Widget";
+const DEMO_WIDGET_TYPE = "demo";
+const ROOM_NAME = "Demo";
+
+const DEMO_WIDGET_HTML = `
+
+
+ Demo Widget
+
+
+
+
+
+
+`;
+
+test.describe("Widget Events", () => {
+ test.use({
+ displayName: "Mike",
+ botCreateOpts: { displayName: "Bot", autoAcceptInvites: true },
+ });
+
+ let demoWidgetUrl: string;
+ test.beforeEach(async ({ webserver }) => {
+ demoWidgetUrl = webserver.start(DEMO_WIDGET_HTML);
+ });
+
+ test("should be updated if user is re-invited into the room with updated state event", async ({
+ page,
+ app,
+ user,
+ bot,
+ }) => {
+ const roomId = await app.client.createRoom({
+ name: ROOM_NAME,
+ invite: [bot.credentials.userId],
+ });
+
+ // setup widget via state event
+ await app.client.sendStateEvent(
+ roomId,
+ "im.vector.modular.widgets",
+ {
+ id: DEMO_WIDGET_ID,
+ creatorUserId: "somebody",
+ type: DEMO_WIDGET_TYPE,
+ name: DEMO_WIDGET_NAME,
+ url: demoWidgetUrl,
+ },
+ DEMO_WIDGET_ID,
+ );
+
+ // set initial layout
+ await app.client.sendStateEvent(
+ roomId,
+ "io.element.widgets.layout",
+ {
+ widgets: {
+ [DEMO_WIDGET_ID]: {
+ container: "top",
+ index: 1,
+ width: 100,
+ height: 0,
+ },
+ },
+ },
+ "",
+ );
+
+ // open the room
+ await app.viewRoomByName(ROOM_NAME);
+
+ // approve capabilities
+ await page.locator(".mx_WidgetCapabilitiesPromptDialog").getByRole("button", { name: "Approve" }).click();
+
+ // bot creates a new room with 'm.room.topic'
+ const roomNew = await bot.createRoom({
+ name: "New room",
+ initial_state: [
+ {
+ type: "m.room.topic",
+ state_key: "",
+ content: {
+ topic: "topic initial",
+ },
+ },
+ ],
+ });
+
+ await bot.inviteUser(roomNew, user.userId);
+
+ // widget should receive 'm.room.topic' event after invite
+ await waitForRoom(page, app.client, roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "net.widget_echo" &&
+ e.getContent().type === "m.room.topic" &&
+ e.getContent().content.topic === "topic initial",
+ );
+ });
+
+ // update the topic
+ await bot.sendStateEvent(
+ roomNew,
+ "m.room.topic",
+ {
+ topic: "topic updated",
+ },
+ "",
+ );
+
+ await bot.inviteUser(roomNew, user.userId);
+
+ // widget should receive updated 'm.room.topic' event after re-invite
+ await waitForRoom(page, app.client, roomId, (room) => {
+ const events = room.getLiveTimeline().getEvents();
+ return events.some(
+ (e) =>
+ e.getType() === "net.widget_echo" &&
+ e.getContent().type === "m.room.topic" &&
+ e.getContent().content.topic === "topic updated",
+ );
+ });
+ });
+});
diff --git a/playwright/e2e/widgets/layout.spec.ts b/playwright/e2e/widgets/layout.spec.ts
new file mode 100644
index 0000000000..a5dd856a93
--- /dev/null
+++ b/playwright/e2e/widgets/layout.spec.ts
@@ -0,0 +1,119 @@
+/*
+Copyright 2022 Oliver Sand
+Copyright 2022 Nordeck IT + Consulting GmbH.
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { test, expect } from "../../element-web-test";
+
+const ROOM_NAME = "Test Room";
+const WIDGET_ID = "fake-widget";
+const WIDGET_HTML = `
+
+
+ Fake Widget
+
+
+ Hello World
+
+
+`;
+
+test.describe("Widget Layout", () => {
+ test.use({
+ displayName: "Sally",
+ });
+
+ let roomId: string;
+ let widgetUrl: string;
+ test.beforeEach(async ({ webserver, app, user }) => {
+ widgetUrl = webserver.start(WIDGET_HTML);
+
+ roomId = await app.client.createRoom({ name: ROOM_NAME });
+
+ // setup widget via state event
+ await app.client.sendStateEvent(
+ roomId,
+ "im.vector.modular.widgets",
+ {
+ id: WIDGET_ID,
+ creatorUserId: "somebody",
+ type: "widget",
+ name: "widget",
+ url: widgetUrl,
+ },
+ WIDGET_ID,
+ );
+
+ // set initial layout
+ await app.client.sendStateEvent(
+ roomId,
+ "io.element.widgets.layout",
+ {
+ widgets: {
+ [WIDGET_ID]: {
+ container: "top",
+ index: 1,
+ width: 100,
+ height: 0,
+ },
+ },
+ },
+ "",
+ );
+
+ // open the room
+ await app.viewRoomByName(ROOM_NAME);
+ });
+
+ test("should be set properly", async ({ page }) => {
+ await expect(page.locator(".mx_AppsDrawer")).toMatchScreenshot("apps-drawer.png");
+ });
+
+ test("manually resize the height of the top container layout", async ({ page }) => {
+ const iframe = page.locator('iframe[title="widget"]');
+ expect((await iframe.boundingBox()).height).toBeLessThan(250);
+
+ await page.locator(".mx_AppsDrawer_resizer_container_handle").hover();
+ await page.mouse.down();
+ await page.mouse.move(0, 550);
+ await page.mouse.up();
+
+ expect((await iframe.boundingBox()).height).toBeGreaterThan(400);
+ });
+
+ test("programmatically resize the height of the top container layout", async ({ page, app }) => {
+ const iframe = page.locator('iframe[title="widget"]');
+ expect((await iframe.boundingBox()).height).toBeLessThan(250);
+
+ await app.client.sendStateEvent(
+ roomId,
+ "io.element.widgets.layout",
+ {
+ widgets: {
+ [WIDGET_ID]: {
+ container: "top",
+ index: 1,
+ width: 100,
+ height: 500,
+ },
+ },
+ },
+ "",
+ );
+
+ await expect.poll(async () => (await iframe.boundingBox()).height).toBeGreaterThan(400);
+ });
+});
diff --git a/cypress/e2e/widgets/stickers.spec.ts b/playwright/e2e/widgets/stickers.spec.ts
similarity index 52%
rename from cypress/e2e/widgets/stickers.spec.ts
rename to playwright/e2e/widgets/stickers.spec.ts
index d3e08f8405..37aaea58ce 100644
--- a/cypress/e2e/widgets/stickers.spec.ts
+++ b/playwright/e2e/widgets/stickers.spec.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
+Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-///
-
-import { HomeserverInstance } from "../../plugins/utils/homeserver";
+import type { Page } from "@playwright/test";
+import { test, expect } from "../../element-web-test";
+import { ElementAppPage } from "../../pages/ElementAppPage";
const STICKER_PICKER_WIDGET_ID = "fake-sticker-picker";
const STICKER_PICKER_WIDGET_NAME = "Fake Stickers";
@@ -33,7 +33,7 @@ const STICKER_MESSAGE = JSON.stringify({
content: {
body: STICKER_NAME,
msgtype: "m.sticker",
- url: "mxc://somewhere",
+ url: "mxc://localhost/somewhere",
},
},
requestId: "1",
@@ -66,108 +66,86 @@ const WIDGET_HTML = `