mirror of
https://github.com/element-hq/element-web
synced 2024-11-25 02:35:48 +03:00
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>
This commit is contained in:
parent
99ca613818
commit
c9008152c5
20 changed files with 1439 additions and 1563 deletions
|
@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<p id="message-response">No response</p>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "get_open_id_token",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
// Listen for a postmessage response
|
||||
window.addEventListener("message", (event) => {
|
||||
document.getElementById("message-response").innerText = JSON.stringify(event.data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" id="target-room-id"/>
|
||||
<input type="text" id="target-user-id"/>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "kick",
|
||||
room_id: document.getElementById("target-room-id").value,
|
||||
user_id: document.getElementById("target-user-id").value,
|
||||
reason: "${KICK_REASON}",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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<MatrixClient>("@bob"), cy.get<string>("@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<MatrixClient>("@bob"), cy.get<string>("@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<MatrixClient>("@bob"), cy.get<string>("@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<MatrixClient>("@bob"), cy.get<string>("@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<MatrixClient>("@bob"), cy.get<string>("@roomId"), cy.get<{}>("@integrationManager")]).then(
|
||||
([targetUser, roomId]) => {
|
||||
const targetUserId = targetUser.getUserId();
|
||||
cy.viewRoomByName(ROOM_NAME);
|
||||
|
||||
openIntegrationManager();
|
||||
sendActionFromIntegrationManager(integrationManagerUrl, roomId, targetUserId);
|
||||
closeIntegrationManager(integrationManagerUrl);
|
||||
expectKickedMessage(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" id="target-room-id"/>
|
||||
<input type="text" id="event-type"/>
|
||||
<input type="text" id="state-key"/>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<p id="message-response">No response</p>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "read_events",
|
||||
room_id: document.getElementById("target-room-id").value,
|
||||
type: document.getElementById("event-type").value,
|
||||
state_key: JSON.parse(document.getElementById("state-key").value),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
// Listen for a postmessage response
|
||||
window.addEventListener("message", (event) => {
|
||||
document.getElementById("message-response").innerText = JSON.stringify(event.data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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<string>("@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<string>("@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<string>("@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<string>("@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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" id="target-room-id"/>
|
||||
<input type="text" id="event-type"/>
|
||||
<input type="text" id="state-key"/>
|
||||
<input type="text" id="event-content"/>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<p id="message-response">No response</p>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "send_event",
|
||||
room_id: document.getElementById("target-room-id").value,
|
||||
type: document.getElementById("event-type").value,
|
||||
state_key: document.getElementById("state-key").value,
|
||||
content: JSON.parse(document.getElementById("event-content").value),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
// Listen for a postmessage response
|
||||
window.addEventListener("message", (event) => {
|
||||
document.getElementById("message-response").innerText = JSON.stringify(event.data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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<string, unknown>,
|
||||
) {
|
||||
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<string>("@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<string>("@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<string>("@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<string>("@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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Demo Widget</title>
|
||||
<script>
|
||||
let sendEventCount = 0
|
||||
window.onmessage = ev => {
|
||||
if (ev.data.action === 'capabilities') {
|
||||
window.parent.postMessage(Object.assign({
|
||||
response: {
|
||||
capabilities: [
|
||||
"org.matrix.msc2762.timeline:*",
|
||||
"org.matrix.msc2762.receive.state_event:m.room.topic",
|
||||
"org.matrix.msc2762.send.event:net.widget_echo"
|
||||
]
|
||||
},
|
||||
}, ev.data), '*');
|
||||
} else if (ev.data.action === 'send_event' && !ev.data.response) {
|
||||
// wraps received event into 'net.widget_echo' and sends back
|
||||
sendEventCount += 1
|
||||
window.parent.postMessage({
|
||||
api: "fromWidget",
|
||||
widgetId: ev.data.widgetId,
|
||||
requestId: 'widget-' + sendEventCount,
|
||||
action: "send_event",
|
||||
data: {
|
||||
type: 'net.widget_echo',
|
||||
content: ev.data.data // sets matrix event to the content returned
|
||||
},
|
||||
}, '*')
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="demo">Demo</button>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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<string>("@widgetEventSent"), cy.get<string>("@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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Widget</title>
|
||||
</head>
|
||||
<body>
|
||||
Hello World
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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<string>("@widgetEventSent"), cy.get<string>("@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);
|
||||
});
|
||||
});
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Demo Widget</title>
|
||||
<script>
|
||||
window.onmessage = ev => {
|
||||
if (ev.data.action === 'capabilities') {
|
||||
window.parent.postMessage(Object.assign({
|
||||
response: {
|
||||
capabilities: []
|
||||
},
|
||||
}, ev.data), '*');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="demo">Demo</button>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// mostly copied from src/utils/WidgetUtils.waitForRoomWidget with small modifications
|
||||
function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: string, add: boolean): Promise<void> {
|
||||
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<string>("@powerLevelsChanged"),
|
||||
cy.get<string>("@botJoined"),
|
||||
cy.get<string>("@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");
|
||||
});
|
||||
});
|
128
playwright/e2e/integration-manager/get-openid-token.spec.ts
Normal file
128
playwright/e2e/integration-manager/get-openid-token.spec.ts
Normal file
|
@ -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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<p id="message-response">No response</p>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "get_open_id_token",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
// Listen for a postmessage response
|
||||
window.addEventListener("message", (event) => {
|
||||
document.getElementById("message-response").innerText = JSON.stringify(event.data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
226
playwright/e2e/integration-manager/kick.spec.ts
Normal file
226
playwright/e2e/integration-manager/kick.spec.ts
Normal file
|
@ -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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" id="target-room-id"/>
|
||||
<input type="text" id="target-user-id"/>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "kick",
|
||||
room_id: document.getElementById("target-room-id").value,
|
||||
user_id: document.getElementById("target-user-id").value,
|
||||
reason: "${KICK_REASON}",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
233
playwright/e2e/integration-manager/read_events.spec.ts
Normal file
233
playwright/e2e/integration-manager/read_events.spec.ts
Normal file
|
@ -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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" id="target-room-id"/>
|
||||
<input type="text" id="event-type"/>
|
||||
<input type="text" id="state-key"/>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<p id="message-response">No response</p>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "read_events",
|
||||
room_id: document.getElementById("target-room-id").value,
|
||||
type: document.getElementById("event-type").value,
|
||||
state_key: JSON.parse(document.getElementById("state-key").value),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
// Listen for a postmessage response
|
||||
window.addEventListener("message", (event) => {
|
||||
document.getElementById("message-response").innerText = JSON.stringify(event.data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
255
playwright/e2e/integration-manager/send_event.spec.ts
Normal file
255
playwright/e2e/integration-manager/send_event.spec.ts
Normal file
|
@ -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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Integration Manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" id="target-room-id"/>
|
||||
<input type="text" id="event-type"/>
|
||||
<input type="text" id="state-key"/>
|
||||
<input type="text" id="event-content"/>
|
||||
<button name="Send" id="send-action">Press to send action</button>
|
||||
<button name="Close" id="close">Press to close</button>
|
||||
<p id="message-response">No response</p>
|
||||
<script>
|
||||
document.getElementById("send-action").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "send_event",
|
||||
room_id: document.getElementById("target-room-id").value,
|
||||
type: document.getElementById("event-type").value,
|
||||
state_key: document.getElementById("state-key").value,
|
||||
content: JSON.parse(document.getElementById("event-content").value),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
document.getElementById("close").onclick = () => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: "close_scalar",
|
||||
},
|
||||
'*',
|
||||
);
|
||||
};
|
||||
// Listen for a postmessage response
|
||||
window.addEventListener("message", (event) => {
|
||||
document.getElementById("message-response").innerText = JSON.stringify(event.data);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
async function sendActionFromIntegrationManager(
|
||||
page: Page,
|
||||
integrationManagerUrl: string,
|
||||
targetRoomId: string,
|
||||
eventType: string,
|
||||
stateKey: string,
|
||||
content: Record<string, unknown>,
|
||||
) {
|
||||
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");
|
||||
});
|
||||
});
|
25
playwright/e2e/integration-manager/utils.ts
Normal file
25
playwright/e2e/integration-manager/utils.ts
Normal file
|
@ -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();
|
||||
}
|
176
playwright/e2e/widgets/events.spec.ts
Normal file
176
playwright/e2e/widgets/events.spec.ts
Normal file
|
@ -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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Demo Widget</title>
|
||||
<script>
|
||||
let sendEventCount = 0
|
||||
window.onmessage = ev => {
|
||||
if (ev.data.action === 'capabilities') {
|
||||
window.parent.postMessage(Object.assign({
|
||||
response: {
|
||||
capabilities: [
|
||||
"org.matrix.msc2762.timeline:*",
|
||||
"org.matrix.msc2762.receive.state_event:m.room.topic",
|
||||
"org.matrix.msc2762.send.event:net.widget_echo"
|
||||
]
|
||||
},
|
||||
}, ev.data), '*');
|
||||
} else if (ev.data.action === 'send_event' && !ev.data.response) {
|
||||
// wraps received event into 'net.widget_echo' and sends back
|
||||
sendEventCount += 1
|
||||
window.parent.postMessage({
|
||||
api: "fromWidget",
|
||||
widgetId: ev.data.widgetId,
|
||||
requestId: 'widget-' + sendEventCount,
|
||||
action: "send_event",
|
||||
data: {
|
||||
type: 'net.widget_echo',
|
||||
content: ev.data.data // sets matrix event to the content returned
|
||||
},
|
||||
}, '*')
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="demo">Demo</button>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
119
playwright/e2e/widgets/layout.spec.ts
Normal file
119
playwright/e2e/widgets/layout.spec.ts
Normal file
|
@ -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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Fake Widget</title>
|
||||
</head>
|
||||
<body>
|
||||
Hello World
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
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 = `
|
|||
</html>
|
||||
`;
|
||||
|
||||
function openStickerPicker() {
|
||||
cy.openMessageComposerOptions().findByRole("menuitem", { name: "Sticker" }).click();
|
||||
async function openStickerPicker(app: ElementAppPage) {
|
||||
const options = await app.openMessageComposerOptions();
|
||||
await options.getByRole("menuitem", { name: "Sticker" }).click();
|
||||
}
|
||||
|
||||
function sendStickerFromPicker() {
|
||||
// Note: Until https://github.com/cypress-io/cypress/issues/136 is fixed we will need
|
||||
// to use `chromeWebSecurity: false` in our cypress config. Not even cy.origin() can
|
||||
// break into the iframe for us :(
|
||||
cy.accessIframe(`iframe[title="${STICKER_PICKER_WIDGET_NAME}"]`).within({}, () => {
|
||||
cy.get("#sendsticker").should("exist").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.
|
||||
cy.get(".mx_AppTileFullWidth#stickers").should("not.exist");
|
||||
await expect(page.locator(".mx_AppTileFullWidth#stickers")).not.toBeVisible();
|
||||
}
|
||||
|
||||
function expectTimelineSticker(roomId: string) {
|
||||
async function expectTimelineSticker(page: Page, roomId: string) {
|
||||
// Make sure it's in the right room
|
||||
cy.get(".mx_EventTile_sticker > a").should("have.attr", "href").and("include", `/${roomId}/`);
|
||||
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.
|
||||
cy.get<HTMLImageElement>(`img[alt="${STICKER_NAME}"][src*="download/somewhere"]`).should("exist");
|
||||
await expect(page.locator(`img[alt="${STICKER_NAME}"]`)).toHaveAttribute(
|
||||
"src",
|
||||
new RegExp("/download/localhost/somewhere"),
|
||||
);
|
||||
}
|
||||
|
||||
describe("Stickers", () => {
|
||||
test.describe("Stickers", () => {
|
||||
test.use({
|
||||
displayName: "Sally",
|
||||
});
|
||||
|
||||
// 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;
|
||||
let homeserver: HomeserverInstance;
|
||||
let userId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.startHomeserver("default").then((data) => {
|
||||
homeserver = data;
|
||||
|
||||
cy.initTestUser(homeserver, "Sally").then((user) => (userId = user.userId));
|
||||
});
|
||||
cy.serveHtmlFile(WIDGET_HTML).then((url) => {
|
||||
stickerPickerUrl = url;
|
||||
});
|
||||
test.beforeEach(async ({ webserver }) => {
|
||||
stickerPickerUrl = webserver.start(WIDGET_HTML);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.stopHomeserver(homeserver);
|
||||
cy.stopWebServers();
|
||||
});
|
||||
test("should send a sticker to multiple rooms", async ({ page, app, user }) => {
|
||||
const roomId1 = await app.client.createRoom({ name: ROOM_NAME_1 });
|
||||
const roomId2 = await app.client.createRoom({ name: ROOM_NAME_2 });
|
||||
|
||||
it("should send a sticker to multiple rooms", () => {
|
||||
cy.createRoom({
|
||||
name: ROOM_NAME_1,
|
||||
}).as("roomId1");
|
||||
cy.createRoom({
|
||||
name: ROOM_NAME_2,
|
||||
}).as("roomId2");
|
||||
cy.setAccountData("m.widgets", {
|
||||
await app.client.setAccountData("m.widgets", {
|
||||
[STICKER_PICKER_WIDGET_ID]: {
|
||||
content: {
|
||||
type: "m.stickerpicker",
|
||||
name: STICKER_PICKER_WIDGET_NAME,
|
||||
url: stickerPickerUrl,
|
||||
creatorUserId: userId,
|
||||
creatorUserId: user.userId,
|
||||
},
|
||||
sender: userId,
|
||||
sender: user.userId,
|
||||
state_key: STICKER_PICKER_WIDGET_ID,
|
||||
type: "m.widget",
|
||||
id: STICKER_PICKER_WIDGET_ID,
|
||||
},
|
||||
}).as("stickers");
|
||||
|
||||
cy.all([
|
||||
cy.get<string>("@roomId1"),
|
||||
cy.get<string>("@roomId2"),
|
||||
cy.get<{}>("@stickers"), // just want to wait for it to be set up
|
||||
]).then(([roomId1, roomId2]) => {
|
||||
cy.viewRoomByName(ROOM_NAME_1);
|
||||
cy.url().should("contain", `/#/room/${roomId1}`);
|
||||
openStickerPicker();
|
||||
sendStickerFromPicker();
|
||||
expectTimelineSticker(roomId1);
|
||||
|
||||
// Ensure that when we switch to a different room that the sticker
|
||||
// goes to the right place
|
||||
cy.viewRoomByName(ROOM_NAME_2);
|
||||
cy.url().should("contain", `/#/room/${roomId2}`);
|
||||
openStickerPicker();
|
||||
sendStickerFromPicker();
|
||||
expectTimelineSticker(roomId2);
|
||||
});
|
||||
|
||||
await app.viewRoomByName(ROOM_NAME_1);
|
||||
await expect(page).toHaveURL(`/#/room/${roomId1}`);
|
||||
await openStickerPicker(app);
|
||||
await sendStickerFromPicker(page);
|
||||
await expectTimelineSticker(page, roomId1);
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
it("should handle a sticker picker widget missing creatorUserId", () => {
|
||||
cy.createRoom({
|
||||
name: ROOM_NAME_1,
|
||||
}).as("roomId1");
|
||||
cy.setAccountData("m.widgets", {
|
||||
test("should handle a sticker picker widget missing creatorUserId", async ({ page, app, user }) => {
|
||||
const roomId1 = await app.client.createRoom({ name: ROOM_NAME_1 });
|
||||
|
||||
await app.client.setAccountData("m.widgets", {
|
||||
[STICKER_PICKER_WIDGET_ID]: {
|
||||
content: {
|
||||
type: "m.stickerpicker",
|
||||
|
@ -175,19 +153,17 @@ describe("Stickers", () => {
|
|||
url: stickerPickerUrl,
|
||||
// No creatorUserId
|
||||
},
|
||||
sender: userId,
|
||||
sender: user.userId,
|
||||
state_key: STICKER_PICKER_WIDGET_ID,
|
||||
type: "m.widget",
|
||||
id: STICKER_PICKER_WIDGET_ID,
|
||||
},
|
||||
}).as("stickers");
|
||||
|
||||
cy.all([cy.get<string>("@roomId1"), cy.get<{}>("@stickers")]).then(([roomId1]) => {
|
||||
cy.viewRoomByName(ROOM_NAME_1);
|
||||
cy.url().should("contain", `/#/room/${roomId1}`);
|
||||
openStickerPicker();
|
||||
sendStickerFromPicker();
|
||||
expectTimelineSticker(roomId1);
|
||||
});
|
||||
|
||||
await app.viewRoomByName(ROOM_NAME_1);
|
||||
await expect(page).toHaveURL(`/#/room/${roomId1}`);
|
||||
await openStickerPicker(app);
|
||||
await sendStickerFromPicker(page);
|
||||
await expectTimelineSticker(page, roomId1);
|
||||
});
|
||||
});
|
169
playwright/e2e/widgets/widget-pip-close.spec.ts
Normal file
169
playwright/e2e/widgets/widget-pip-close.spec.ts
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
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 type { IWidget } from "matrix-widget-api/src/interfaces/IWidget";
|
||||
import type { MatrixEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { Client } from "../../pages/client";
|
||||
|
||||
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 = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Demo Widget</title>
|
||||
<script>
|
||||
window.onmessage = ev => {
|
||||
if (ev.data.action === 'capabilities') {
|
||||
window.parent.postMessage(Object.assign({
|
||||
response: {
|
||||
capabilities: []
|
||||
},
|
||||
}, ev.data), '*');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="demo">Demo</button>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// mostly copied from src/utils/WidgetUtils.waitForRoomWidget with small modifications
|
||||
async function waitForRoomWidget(client: Client, widgetId: string, roomId: string, add: boolean): Promise<void> {
|
||||
await client.evaluate(
|
||||
(matrixClient, { widgetId, roomId, add }) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
function eventsInIntendedState(evList: MatrixEvent[]) {
|
||||
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("RoomState.events" as RoomStateEvent.Events, onRoomStateEvents);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
matrixClient.on("RoomState.events" as RoomStateEvent.Events, onRoomStateEvents);
|
||||
});
|
||||
},
|
||||
{ widgetId, roomId, add },
|
||||
);
|
||||
}
|
||||
|
||||
test.describe("Widget PIP", () => {
|
||||
test.use({
|
||||
displayName: "Mike",
|
||||
botCreateOpts: { displayName: "Bot", autoAcceptInvites: false },
|
||||
});
|
||||
|
||||
let demoWidgetUrl: string;
|
||||
test.beforeEach(async ({ webserver }) => {
|
||||
demoWidgetUrl = webserver.start(DEMO_WIDGET_HTML);
|
||||
});
|
||||
|
||||
for (const userRemove of ["leave", "kick", "ban"] as const) {
|
||||
test(`should be closed on ${userRemove}`, async ({ page, app, bot, user }) => {
|
||||
const roomId = await app.client.createRoom({
|
||||
name: ROOM_NAME,
|
||||
invite: [bot.credentials.userId],
|
||||
});
|
||||
|
||||
// sets bot to Admin and user to Moderator
|
||||
await app.client.sendStateEvent(roomId, "m.room.power_levels", {
|
||||
users: {
|
||||
[user.userId]: 50,
|
||||
[bot.credentials.userId]: 100,
|
||||
},
|
||||
});
|
||||
|
||||
// bot joins the room
|
||||
await bot.joinRoom(roomId);
|
||||
|
||||
// setup widget via state event
|
||||
const content: IWidget = {
|
||||
id: DEMO_WIDGET_ID,
|
||||
creatorUserId: "somebody",
|
||||
type: DEMO_WIDGET_TYPE,
|
||||
name: DEMO_WIDGET_NAME,
|
||||
url: demoWidgetUrl,
|
||||
};
|
||||
await app.client.sendStateEvent(roomId, "im.vector.modular.widgets", content, DEMO_WIDGET_ID);
|
||||
|
||||
// open the room
|
||||
await app.viewRoomByName(ROOM_NAME);
|
||||
|
||||
// wait for widget state event
|
||||
await waitForRoomWidget(app.client, DEMO_WIDGET_ID, roomId, true);
|
||||
|
||||
// activate widget in pip mode
|
||||
await page.evaluate(
|
||||
({ widgetId, roomId }) => {
|
||||
window.mxActiveWidgetStore.setWidgetPersistence(widgetId, roomId, true);
|
||||
},
|
||||
{
|
||||
widgetId: DEMO_WIDGET_ID,
|
||||
roomId,
|
||||
},
|
||||
);
|
||||
|
||||
// checks that pip window is opened
|
||||
await expect(page.locator(".mx_WidgetPip")).toBeVisible();
|
||||
|
||||
// checks that widget is opened in pip
|
||||
const iframe = page.frameLocator(`iframe[title="${DEMO_WIDGET_NAME}"]`);
|
||||
await expect(iframe.locator("#demo")).toBeVisible();
|
||||
|
||||
const userId = user.userId;
|
||||
if (userRemove == "leave") {
|
||||
await app.client.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
|
||||
await expect(iframe.locator(".mx_WidgetPip")).not.toBeVisible();
|
||||
});
|
||||
}
|
||||
});
|
3
playwright/global.d.ts
vendored
3
playwright/global.d.ts
vendored
|
@ -25,6 +25,9 @@ declare global {
|
|||
mxSettingsStore: {
|
||||
setValue(settingName: string, roomId: string | null, level: SettingLevel, value: any): Promise<void>;
|
||||
};
|
||||
mxActiveWidgetStore: {
|
||||
setWidgetPersistence(widgetId: string, roomId: string | null, val: boolean): void;
|
||||
};
|
||||
matrixcs: typeof Matrix;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -314,6 +314,54 @@ export class Client {
|
|||
}, credentials);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets account data for the user.
|
||||
* @param type The type of account data to set
|
||||
* @param content The content to set
|
||||
*/
|
||||
public async setAccountData(type: string, content: IContent): Promise<void> {
|
||||
const client = await this.prepareClient();
|
||||
return client.evaluate(
|
||||
async (client, { type, content }) => {
|
||||
await client.setAccountData(type, content);
|
||||
},
|
||||
{ type, content },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a state event into the room.
|
||||
* @param roomId ID of the room to send the event into
|
||||
* @param eventType type of event to send
|
||||
* @param content the event content to send
|
||||
* @param stateKey the state key to use
|
||||
*/
|
||||
public async sendStateEvent(
|
||||
roomId: string,
|
||||
eventType: string,
|
||||
content: IContent,
|
||||
stateKey?: string,
|
||||
): Promise<ISendEventResponse> {
|
||||
const client = await this.prepareClient();
|
||||
return client.evaluate(
|
||||
async (client, { roomId, eventType, content, stateKey }) => {
|
||||
return client.sendStateEvent(roomId, eventType, content, stateKey);
|
||||
},
|
||||
{ roomId, eventType, content, stateKey },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves the given room.
|
||||
* @param roomId ID of the room to leave
|
||||
*/
|
||||
public async leave(roomId: string): Promise<void> {
|
||||
const client = await this.prepareClient();
|
||||
return client.evaluate(async (client, roomId) => {
|
||||
await client.leave(roomId);
|
||||
}, roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the directory visibility for a room.
|
||||
* @param roomId ID of the room to set the directory visibility for
|
||||
|
|
|
@ -33,7 +33,7 @@ export class Webserver {
|
|||
|
||||
const address = this.server.address() as AddressInfo;
|
||||
console.log(`Started webserver at ${address.address}:${address.port}`);
|
||||
return `http://localhost:${address.port}/`;
|
||||
return `http://localhost:${address.port}`;
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
Loading…
Reference in a new issue