From 82840a19f9cea4b6cc765ba44e51bdc5059d6588 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Tue, 9 Jan 2024 11:58:40 +0530 Subject: [PATCH] Playwright: Convert /e2e/polls (#12065) * Convert tests * Conver tests * Move type import down * Remove reference to cypress types * Update screenshot * Remove client.evaluate * More changes * Use credentials from bot object * Use credentials * Remove comment * Update screenshots * Use sendMessage method * Create dummy test * Use userId from credentials --- cypress/e2e/dummy.spec.ts | 19 + cypress/e2e/polls/pollHistory.spec.ts | 191 --------- cypress/e2e/polls/polls.spec.ts | 372 ------------------ playwright/e2e/polls/pollHistory.spec.ts | 149 +++++++ playwright/e2e/polls/polls.spec.ts | 333 ++++++++++++++++ .../Polls-Timeline-tile-no-votes-linux.png | Bin 0 -> 16778 bytes ...iew-with-a-poll-on-bubble-layout-linux.png | Bin 0 -> 24390 bytes ...View-with-a-poll-on-group-layout-linux.png | Bin 0 -> 24316 bytes 8 files changed, 501 insertions(+), 563 deletions(-) create mode 100644 cypress/e2e/dummy.spec.ts delete mode 100644 cypress/e2e/polls/pollHistory.spec.ts delete mode 100644 cypress/e2e/polls/polls.spec.ts create mode 100644 playwright/e2e/polls/pollHistory.spec.ts create mode 100644 playwright/e2e/polls/polls.spec.ts create mode 100644 playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png create mode 100644 playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png create mode 100644 playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png diff --git a/cypress/e2e/dummy.spec.ts b/cypress/e2e/dummy.spec.ts new file mode 100644 index 0000000000..6940c642b5 --- /dev/null +++ b/cypress/e2e/dummy.spec.ts @@ -0,0 +1,19 @@ +/* +Copyright 2024 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. +*/ + +/// + +it("Dummy test to make CI pass", () => {}); diff --git a/cypress/e2e/polls/pollHistory.spec.ts b/cypress/e2e/polls/pollHistory.spec.ts deleted file mode 100644 index dec4fed5af..0000000000 --- a/cypress/e2e/polls/pollHistory.spec.ts +++ /dev/null @@ -1,191 +0,0 @@ -/* -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 { HomeserverInstance } from "../../plugins/utils/homeserver"; -import { MatrixClient } from "../../global"; - -describe("Poll history", () => { - let homeserver: HomeserverInstance; - - type CreatePollOptions = { - title: string; - options: { - "id": string; - "org.matrix.msc1767.text": string; - }[]; - }; - const createPoll = async ({ title, options }: CreatePollOptions, roomId, client: MatrixClient) => { - return await client.sendEvent(roomId, "org.matrix.msc3381.poll.start", { - "org.matrix.msc3381.poll.start": { - question: { - "org.matrix.msc1767.text": title, - "body": title, - "msgtype": "m.text", - }, - kind: "org.matrix.msc3381.poll.disclosed", - max_selections: 1, - answers: options, - }, - "org.matrix.msc1767.text": "poll fallback text", - }); - }; - - const botVoteForOption = async ( - bot: MatrixClient, - roomId: string, - pollId: string, - optionId: string, - ): Promise => { - // We can't use the js-sdk types for this stuff directly, so manually construct the event. - await bot.sendEvent(roomId, "org.matrix.msc3381.poll.response", { - "m.relates_to": { - rel_type: "m.reference", - event_id: pollId, - }, - "org.matrix.msc3381.poll.response": { - answers: [optionId], - }, - }); - }; - - const endPoll = async (bot: MatrixClient, roomId: string, pollId: string): Promise => { - // We can't use the js-sdk types for this stuff directly, so manually construct the event. - await bot.sendEvent(roomId, "org.matrix.msc3381.poll.end", { - "m.relates_to": { - rel_type: "m.reference", - event_id: pollId, - }, - "org.matrix.msc1767.text": "The poll has ended", - }); - }; - - function openPollHistory(): void { - cy.findByRole("button", { name: "Room info" }).click(); - cy.get(".mx_RoomSummaryCard").within(() => { - cy.findByRole("menuitem", { name: "Poll history" }).click(); - }); - } - - beforeEach(() => { - cy.window().then((win) => { - win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests - }); - cy.startHomeserver("default").then((data) => { - homeserver = data; - - cy.initTestUser(homeserver, "Tom"); - }); - }); - - afterEach(() => { - cy.stopHomeserver(homeserver); - }); - - it("Should display active and past polls", () => { - let bot: MatrixClient; - cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => { - bot = _bot; - }); - - const pollParams1 = { - title: "Does the polls feature work?", - options: ["Yes", "No", "Maybe"].map((option) => ({ - "id": option, - "org.matrix.msc1767.text": option, - })), - }; - - const pollParams2 = { - title: "Which way", - options: ["Left", "Right"].map((option) => ({ - "id": option, - "org.matrix.msc1767.text": option, - })), - }; - - cy.createRoom({}).as("roomId"); - - cy.get("@roomId").then((roomId) => { - cy.inviteUser(roomId, bot.getUserId()); - cy.visit("/#/room/" + roomId); - // wait until Bob joined - cy.findByText("BotBob joined the room").should("exist"); - }); - - // active poll - cy.get("@roomId") - .then(async (roomId) => { - const { event_id: pollId } = await createPoll(pollParams1, roomId, bot); - await botVoteForOption(bot, roomId, pollId, pollParams1.options[1].id); - return pollId; - }) - .as("pollId1"); - - // ended poll - cy.get("@roomId") - .then(async (roomId) => { - const { event_id: pollId } = await createPoll(pollParams2, roomId, bot); - await botVoteForOption(bot, roomId, pollId, pollParams1.options[1].id); - await endPoll(bot, roomId, pollId); - return pollId; - }) - .as("pollId2"); - - openPollHistory(); - - // these polls are also in the timeline - // focus on the poll history dialog - cy.get(".mx_Dialog").within(() => { - // active poll is in active polls list - // open poll detail - cy.findByText(pollParams1.title).click(); - - // vote in the poll - cy.findByText("Yes").click(); - cy.findByTestId("totalVotes").within(() => { - cy.findByText("Based on 2 votes"); - }); - - // navigate back to list - cy.get(".mx_PollHistory_header").within(() => { - cy.findByRole("button", { name: "Active polls" }).click(); - }); - - // go to past polls list - cy.findByText("Past polls").click(); - - cy.findByText(pollParams2.title).should("exist"); - }); - - // end poll1 while dialog is open - cy.all([cy.get("@roomId"), cy.get("@pollId1")]).then(async ([roomId, pollId]) => { - return endPoll(bot, roomId, pollId); - }); - - cy.get(".mx_Dialog").within(() => { - // both ended polls are in past polls list - cy.findByText(pollParams2.title).should("exist"); - cy.findByText(pollParams1.title).should("exist"); - - cy.findByText("Active polls").click(); - - // no more active polls - cy.findByText("There are no active polls in this room").should("exist"); - }); - }); -}); diff --git a/cypress/e2e/polls/polls.spec.ts b/cypress/e2e/polls/polls.spec.ts deleted file mode 100644 index 1a6682a642..0000000000 --- a/cypress/e2e/polls/polls.spec.ts +++ /dev/null @@ -1,372 +0,0 @@ -/* -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 { HomeserverInstance } from "../../plugins/utils/homeserver"; -import { MatrixClient } from "../../global"; -import { SettingLevel } from "../../../src/settings/SettingLevel"; -import { Layout } from "../../../src/settings/enums/Layout"; -import Chainable = Cypress.Chainable; - -const hidePercyCSS = ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }"; - -describe("Polls", () => { - let homeserver: HomeserverInstance; - - type CreatePollOptions = { - title: string; - options: string[]; - }; - const createPoll = ({ title, options }: CreatePollOptions) => { - if (options.length < 2) { - throw new Error("Poll must have at least two options"); - } - cy.get(".mx_PollCreateDialog").within((pollCreateDialog) => { - cy.findByRole("textbox", { name: "Question or topic" }).type(title); - - options.forEach((option, index) => { - const optionId = `#pollcreate_option_${index}`; - - // click 'add option' button if needed - if (pollCreateDialog.find(optionId).length === 0) { - cy.findByRole("button", { name: "Add option" }).scrollIntoView().click(); - } - cy.get(optionId).scrollIntoView().type(option); - }); - }); - cy.get(".mx_Dialog").within(() => { - cy.findByRole("button", { name: "Create Poll" }).click(); - }); - }; - - const getPollTile = (pollId: string): Chainable => { - return cy.get(`.mx_EventTile[data-scroll-tokens="${pollId}"]`); - }; - - const getPollOption = (pollId: string, optionText: string): Chainable => { - return getPollTile(pollId).contains(".mx_PollOption .mx_StyledRadioButton", optionText); - }; - - const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => { - getPollOption(pollId, optionText).within(() => { - cy.get(".mx_PollOption_optionVoteCount").should("contain", `${votes} vote`); - }); - }; - - const botVoteForOption = (bot: MatrixClient, roomId: string, pollId: string, optionText: string): void => { - getPollOption(pollId, optionText).within((ref) => { - cy.findByRole("radio") - .invoke("attr", "value") - .then((optionId) => { - // We can't use the js-sdk types for this stuff directly, so manually construct the event. - bot.sendEvent(roomId, "org.matrix.msc3381.poll.response", { - "m.relates_to": { - rel_type: "m.reference", - event_id: pollId, - }, - "org.matrix.msc3381.poll.response": { - answers: [optionId], - }, - }); - }); - }); - }; - - beforeEach(() => { - cy.window().then((win) => { - win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests - }); - cy.startHomeserver("default").then((data) => { - homeserver = data; - - cy.initTestUser(homeserver, "Tom"); - }); - }); - - afterEach(() => { - cy.stopHomeserver(homeserver); - }); - - it("should be creatable and votable", () => { - let bot: MatrixClient; - cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => { - bot = _bot; - }); - - let roomId: string; - cy.createRoom({}).then((_roomId) => { - roomId = _roomId; - cy.inviteUser(roomId, bot.getUserId()); - cy.visit("/#/room/" + roomId); - // wait until Bob joined - cy.findByText("BotBob joined the room").should("exist"); - }); - - cy.openMessageComposerOptions().within(() => { - cy.findByRole("menuitem", { name: "Poll" }).click(); - }); - - // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688 - //cy.get(".mx_CompoundDialog").percySnapshotElement("Polls Composer"); - - const pollParams = { - title: "Does the polls feature work?", - // Since we're going to take a screenshot anyways, we include some - // non-ASCII characters here to stress test the app's font config - // while we're at it. - options: ["Yes", "Noo⃐o⃑o⃩o⃪o⃫o⃬o⃭o⃮o⃯", "のらねこ Maybe?"], - }; - createPoll(pollParams); - - // Wait for message to send, get its ID and save as @pollId - cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens") - .as("pollId"); - - cy.get("@pollId").then((pollId) => { - getPollTile(pollId).percySnapshotElement("Polls Timeline tile - no votes", { percyCSS: hidePercyCSS }); - - // Bot votes 'Maybe' in the poll - botVoteForOption(bot, roomId, pollId, pollParams.options[2]); - - // no votes shown until I vote, check bots vote has arrived - cy.get(".mx_MPollBody_totalVotes").within(() => { - cy.findByText("1 vote cast. Vote to see the results"); - }); - - // vote 'Maybe' - getPollOption(pollId, pollParams.options[2]).click("topLeft"); - // both me and bot have voted Maybe - expectPollOptionVoteCount(pollId, pollParams.options[2], 2); - - // change my vote to 'Yes' - getPollOption(pollId, pollParams.options[0]).click("topLeft"); - - // 1 vote for yes - expectPollOptionVoteCount(pollId, pollParams.options[0], 1); - // 1 vote for maybe - expectPollOptionVoteCount(pollId, pollParams.options[2], 1); - - // Bot updates vote to 'No' - botVoteForOption(bot, roomId, pollId, pollParams.options[1]); - - // 1 vote for yes - expectPollOptionVoteCount(pollId, pollParams.options[0], 1); - // 1 vote for no - expectPollOptionVoteCount(pollId, pollParams.options[0], 1); - // 0 for maybe - expectPollOptionVoteCount(pollId, pollParams.options[2], 0); - }); - }); - - it("should be editable from context menu if no votes have been cast", () => { - let bot: MatrixClient; - cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => { - bot = _bot; - }); - - let roomId: string; - cy.createRoom({}).then((_roomId) => { - roomId = _roomId; - cy.inviteUser(roomId, bot.getUserId()); - cy.visit("/#/room/" + roomId); - }); - - cy.openMessageComposerOptions().within(() => { - cy.findByRole("menuitem", { name: "Poll" }).click(); - }); - - const pollParams = { - title: "Does the polls feature work?", - options: ["Yes", "No", "Maybe"], - }; - createPoll(pollParams); - - // Wait for message to send, get its ID and save as @pollId - cy.get(".mx_RoomView_body .mx_EventTile") - .contains(".mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens") - .as("pollId"); - - cy.get("@pollId").then((pollId) => { - // Open context menu - getPollTile(pollId).rightclick(); - - // Select edit item - cy.findByRole("menuitem", { name: "Edit" }).click(); - - // Expect poll editing dialog - cy.get(".mx_PollCreateDialog"); - }); - }); - - it("should not be editable from context menu if votes have been cast", () => { - let bot: MatrixClient; - cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => { - bot = _bot; - }); - - let roomId: string; - cy.createRoom({}).then((_roomId) => { - roomId = _roomId; - cy.inviteUser(roomId, bot.getUserId()); - cy.visit("/#/room/" + roomId); - }); - - cy.openMessageComposerOptions().within(() => { - cy.findByRole("menuitem", { name: "Poll" }).click(); - }); - - const pollParams = { - title: "Does the polls feature work?", - options: ["Yes", "No", "Maybe"], - }; - createPoll(pollParams); - - // Wait for message to send, get its ID and save as @pollId - cy.get(".mx_RoomView_body .mx_EventTile") - .contains(".mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens") - .as("pollId"); - - cy.get("@pollId").then((pollId) => { - // Bot votes 'Maybe' in the poll - botVoteForOption(bot, roomId, pollId, pollParams.options[2]); - - // wait for bot's vote to arrive - cy.get(".mx_MPollBody_totalVotes").should("contain", "1 vote cast"); - - // Open context menu - getPollTile(pollId).rightclick(); - - // Select edit item - cy.findByRole("menuitem", { name: "Edit" }).click(); - - // Expect error dialog - cy.get(".mx_ErrorDialog"); - }); - }); - - it("should be displayed correctly in thread panel", () => { - let botBob: MatrixClient; - let botCharlie: MatrixClient; - cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => { - botBob = _bot; - }); - cy.getBot(homeserver, { displayName: "BotCharlie" }).then((_bot) => { - botCharlie = _bot; - }); - - let roomId: string; - cy.createRoom({}).then((_roomId) => { - roomId = _roomId; - cy.inviteUser(roomId, botBob.getUserId()); - cy.inviteUser(roomId, botCharlie.getUserId()); - cy.visit("/#/room/" + roomId); - // wait until the bots joined - cy.findByText("BotBob and one other were invited and joined", { timeout: 10000 }).should("exist"); - }); - - cy.openMessageComposerOptions().within(() => { - cy.findByRole("menuitem", { name: "Poll" }).click(); - }); - - const pollParams = { - title: "Does the polls feature work?", - options: ["Yes", "No", "Maybe"], - }; - createPoll(pollParams); - - // Wait for message to send, get its ID and save as @pollId - cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title) - .invoke("attr", "data-scroll-tokens") - .as("pollId"); - - cy.get("@pollId").then((pollId) => { - // Bob starts thread on the poll - botBob.sendMessage(roomId, pollId, { - body: "Hello there", - msgtype: "m.text", - }); - - // open the thread summary - cy.findByRole("button", { name: "Open thread" }).click(); - - // Bob votes 'Maybe' in the poll - botVoteForOption(botBob, roomId, pollId, pollParams.options[2]); - // Charlie votes 'No' - botVoteForOption(botCharlie, roomId, pollId, pollParams.options[1]); - - // no votes shown until I vote, check votes have arrived in main tl - cy.get(".mx_RoomView_body .mx_MPollBody_totalVotes").within(() => { - cy.findByText("2 votes cast. Vote to see the results").should("exist"); - }); - // and thread view - cy.get(".mx_ThreadView .mx_MPollBody_totalVotes").within(() => { - cy.findByText("2 votes cast. Vote to see the results").should("exist"); - }); - - // Take snapshots of poll on ThreadView - cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); - cy.get(".mx_ThreadView .mx_EventTile[data-layout='bubble']").should("be.visible"); - cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with a poll on bubble layout", { - percyCSS: hidePercyCSS, - }); - cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group); - cy.get(".mx_ThreadView .mx_EventTile[data-layout='group']").should("be.visible"); - cy.get(".mx_ThreadView").percySnapshotElement("ThreadView with a poll on group layout", { - percyCSS: hidePercyCSS, - }); - - cy.get(".mx_RoomView_body").within(() => { - // vote 'Maybe' in the main timeline poll - getPollOption(pollId, pollParams.options[2]).click("topLeft"); - // both me and bob have voted Maybe - expectPollOptionVoteCount(pollId, pollParams.options[2], 2); - }); - - cy.get(".mx_ThreadView").within(() => { - // votes updated in thread view too - expectPollOptionVoteCount(pollId, pollParams.options[2], 2); - // change my vote to 'Yes' - getPollOption(pollId, pollParams.options[0]).click("topLeft"); - }); - - // Bob updates vote to 'No' - botVoteForOption(botBob, roomId, pollId, pollParams.options[1]); - - // me: yes, bob: no, charlie: no - const expectVoteCounts = () => { - // I voted yes - expectPollOptionVoteCount(pollId, pollParams.options[0], 1); - // Bob and Charlie voted no - expectPollOptionVoteCount(pollId, pollParams.options[1], 2); - // 0 for maybe - expectPollOptionVoteCount(pollId, pollParams.options[2], 0); - }; - - // check counts are correct in main timeline tile - cy.get(".mx_RoomView_body").within(() => { - expectVoteCounts(); - }); - // and in thread view tile - cy.get(".mx_ThreadView").within(() => { - expectVoteCounts(); - }); - }); - }); -}); diff --git a/playwright/e2e/polls/pollHistory.spec.ts b/playwright/e2e/polls/pollHistory.spec.ts new file mode 100644 index 0000000000..458bb544c7 --- /dev/null +++ b/playwright/e2e/polls/pollHistory.spec.ts @@ -0,0 +1,149 @@ +/* +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"; +import type { Page } from "@playwright/test"; +import type { Bot } from "../../pages/bot"; +import type { Client } from "../../pages/client"; + +test.describe("Poll history", () => { + type CreatePollOptions = { + title: string; + options: { + "id": string; + "org.matrix.msc1767.text": string; + }[]; + }; + const createPoll = async (createOptions: CreatePollOptions, roomId: string, client: Client) => { + return client.sendEvent(roomId, null, "org.matrix.msc3381.poll.start", { + "org.matrix.msc3381.poll.start": { + question: { + "org.matrix.msc1767.text": createOptions.title, + "body": createOptions.title, + "msgtype": "m.text", + }, + kind: "org.matrix.msc3381.poll.disclosed", + max_selections: 1, + answers: createOptions.options, + }, + "org.matrix.msc1767.text": "poll fallback text", + }); + }; + + const botVoteForOption = async (bot: Bot, roomId: string, pollId: string, optionId: string): Promise => { + // We can't use the js-sdk types for this stuff directly, so manually construct the event. + await bot.sendEvent(roomId, null, "org.matrix.msc3381.poll.response", { + "m.relates_to": { + rel_type: "m.reference", + event_id: pollId, + }, + "org.matrix.msc3381.poll.response": { + answers: [optionId], + }, + }); + }; + + const endPoll = async (bot: Bot, roomId: string, pollId: string): Promise => { + // We can't use the js-sdk types for this stuff directly, so manually construct the event. + await bot.sendEvent(roomId, null, "org.matrix.msc3381.poll.end", { + "m.relates_to": { + rel_type: "m.reference", + event_id: pollId, + }, + "org.matrix.msc1767.text": "The poll has ended", + }); + }; + + async function openPollHistory(page: Page): Promise { + await page.getByRole("button", { name: "Room info" }).click(); + await page.locator(".mx_RoomSummaryCard").getByRole("menuitem", { name: "Poll history" }).click(); + } + + test.use({ + displayName: "Tom", + botCreateOpts: { displayName: "BotBob" }, + }); + + test.beforeEach(async ({ page }) => { + await page.addInitScript(() => { + // Collapse left panel for these tests + window.localStorage.setItem("mx_lhs_size", "0"); + }); + }); + + test("Should display active and past polls", async ({ page, app, user, bot }) => { + const pollParams1 = { + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"].map((option) => ({ + "id": option, + "org.matrix.msc1767.text": option, + })), + }; + + const pollParams2 = { + title: "Which way", + options: ["Left", "Right"].map((option) => ({ + "id": option, + "org.matrix.msc1767.text": option, + })), + }; + + const roomId = await app.client.createRoom({}); + + await app.client.inviteUser(roomId, bot.credentials.userId); + await page.goto("/#/room/" + roomId); + // wait until Bob joined + await expect(page.getByText("BotBob joined the room")).toBeAttached(); + + // active poll + const { event_id: pollId1 } = await createPoll(pollParams1, roomId, bot); + await botVoteForOption(bot, roomId, pollId1, pollParams1.options[1].id); + + // ended poll + const { event_id: pollId2 } = await createPoll(pollParams2, roomId, bot); + await botVoteForOption(bot, roomId, pollId2, pollParams1.options[1].id); + await endPoll(bot, roomId, pollId2); + + await openPollHistory(page); + + // these polls are also in the timeline + // focus on the poll history dialog + const dialog = page.locator(".mx_Dialog"); + + // active poll is in active polls list + // open poll detail + await dialog.getByText(pollParams1.title).click(); + await dialog.getByText("Yes").click(); + // vote in the poll + await expect(dialog.getByTestId("totalVotes").getByText("Based on 2 votes")).toBeAttached(); + // navigate back to list + await dialog.locator(".mx_PollHistory_header").getByRole("button", { name: "Active polls" }).click(); + + // go to past polls list + await dialog.getByText("Past polls").click(); + + await expect(dialog.getByText(pollParams2.title)).toBeAttached(); + + // end poll1 while dialog is open + await endPoll(bot, roomId, pollId1); + + await expect(dialog.getByText(pollParams2.title)).toBeAttached(); + await expect(dialog.getByText(pollParams1.title)).toBeAttached(); + dialog.getByText("Active polls").click(); + + // no more active polls + await expect(page.getByText("There are no active polls in this room")).toBeAttached(); + }); +}); diff --git a/playwright/e2e/polls/polls.spec.ts b/playwright/e2e/polls/polls.spec.ts new file mode 100644 index 0000000000..c4a8ae1bbe --- /dev/null +++ b/playwright/e2e/polls/polls.spec.ts @@ -0,0 +1,333 @@ +/* +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"; +import { Bot } from "../../pages/bot"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import { Layout } from "../../../src/settings/enums/Layout"; +import type { Locator, Page } from "@playwright/test"; + +test.describe("Polls", () => { + type CreatePollOptions = { + title: string; + options: string[]; + }; + const createPoll = async (page: Page, { title, options }: CreatePollOptions) => { + if (options.length < 2) { + throw new Error("Poll must have at least two options"); + } + const dialog = page.locator(".mx_PollCreateDialog"); + await dialog.getByRole("textbox", { name: "Question or topic" }).fill(title); + for (const [index, value] of options.entries()) { + const optionIdLocator = dialog.locator(`#pollcreate_option_${index}`); + // click 'add option' button if needed + if ((await optionIdLocator.count()) === 0) { + const button = dialog.getByRole("button", { name: "Add option" }); + await button.scrollIntoViewIfNeeded(); + await button.click(); + } + await optionIdLocator.scrollIntoViewIfNeeded(); + await optionIdLocator.fill(value); + } + await page.locator(".mx_Dialog").getByRole("button", { name: "Create Poll" }).click(); + }; + + const getPollTile = (page: Page, pollId: string, optLocator?: Locator): Locator => { + return (optLocator ?? page).locator(`.mx_EventTile[data-scroll-tokens="${pollId}"]`); + }; + + const getPollOption = (page: Page, pollId: string, optionText: string, optLocator?: Locator): Locator => { + return getPollTile(page, pollId, optLocator) + .locator(".mx_PollOption .mx_StyledRadioButton") + .filter({ hasText: optionText }); + }; + + const expectPollOptionVoteCount = async ( + page: Page, + pollId: string, + optionText: string, + votes: number, + optLocator?: Locator, + ): Promise => { + await expect( + getPollOption(page, pollId, optionText, optLocator).locator(".mx_PollOption_optionVoteCount"), + ).toContainText(`${votes} vote`); + }; + + const botVoteForOption = async ( + page: Page, + bot: Bot, + roomId: string, + pollId: string, + optionText: string, + ): Promise => { + const locator = getPollOption(page, pollId, optionText); + const optionId = await locator.first().getByRole("radio").getAttribute("value"); + + // We can't use the js-sdk types for this stuff directly, so manually construct the event. + await bot.sendEvent(roomId, null, "org.matrix.msc3381.poll.response", { + "m.relates_to": { + rel_type: "m.reference", + event_id: pollId, + }, + "org.matrix.msc3381.poll.response": { + answers: [optionId], + }, + }); + }; + + test.use({ + displayName: "Tom", + botCreateOpts: { displayName: "BotBob" }, + }); + + test.beforeEach(async ({ page }) => { + await page.addInitScript(() => { + // Collapse left panel for these tests + window.localStorage.setItem("mx_lhs_size", "0"); + }); + }); + + test("should be creatable and votable", async ({ page, app, bot, user }) => { + const roomId: string = await app.client.createRoom({}); + await app.client.inviteUser(roomId, bot.credentials.userId); + await page.goto("/#/room/" + roomId); + // wait until Bob joined + await expect(page.getByText("BotBob joined the room")).toBeAttached(); + + const locator = await app.openMessageComposerOptions(); + await locator.getByRole("menuitem", { name: "Poll" }).click(); + + // Disabled because flaky - see https://github.com/vector-im/element-web/issues/24688 + //cy.get(".mx_CompoundDialog").percySnapshotElement("Polls Composer"); + + const pollParams = { + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe?"], + }; + await createPoll(page, pollParams); + + // Wait for message to send, get its ID and save as @pollId + const pollId = await page + .locator(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]") + .filter({ hasText: pollParams.title }) + .getAttribute("data-scroll-tokens"); + await expect(getPollTile(page, pollId)).toMatchScreenshot("Polls_Timeline_tile_no_votes.png", { + mask: [page.locator(".mx_MessageTimestamp")], + }); + + // Bot votes 'Maybe' in the poll + await botVoteForOption(page, bot, roomId, pollId, pollParams.options[2]); + + // no votes shown until I vote, check bots vote has arrived + await expect( + page.locator(".mx_MPollBody_totalVotes").getByText("1 vote cast. Vote to see the results"), + ).toBeAttached(); + + // vote 'Maybe' + await getPollOption(page, pollId, pollParams.options[2]).click(); + // both me and bot have voted Maybe + await expectPollOptionVoteCount(page, pollId, pollParams.options[2], 2); + + // change my vote to 'Yes' + await getPollOption(page, pollId, pollParams.options[0]).click(); + + // 1 vote for yes + await expectPollOptionVoteCount(page, pollId, pollParams.options[0], 1); + // 1 vote for maybe + await expectPollOptionVoteCount(page, pollId, pollParams.options[2], 1); + + // Bot updates vote to 'No' + await botVoteForOption(page, bot, roomId, pollId, pollParams.options[1]); + + // 1 vote for yes + await expectPollOptionVoteCount(page, pollId, pollParams.options[0], 1); + // 1 vote for no + await expectPollOptionVoteCount(page, pollId, pollParams.options[0], 1); + // 0 for maybe + await expectPollOptionVoteCount(page, pollId, pollParams.options[2], 0); + }); + + test("should be editable from context menu if no votes have been cast", async ({ page, app, user, bot }) => { + const roomId: string = await app.client.createRoom({}); + await app.client.inviteUser(roomId, bot.credentials.userId); + await page.goto("/#/room/" + roomId); + + const locator = await app.openMessageComposerOptions(); + await locator.getByRole("menuitem", { name: "Poll" }).click(); + + const pollParams = { + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"], + }; + await createPoll(page, pollParams); + + // Wait for message to send, get its ID and save as @pollId + const pollId = await page + .locator(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]") + .filter({ hasText: pollParams.title }) + .getAttribute("data-scroll-tokens"); + + // Open context menu + await getPollTile(page, pollId).click({ button: "right" }); + + // Select edit item + await page.getByRole("menuitem", { name: "Edit" }).click(); + + // Expect poll editing dialog + await expect(page.locator(".mx_PollCreateDialog")).toBeAttached(); + }); + + test("should not be editable from context menu if votes have been cast", async ({ page, app, user, bot }) => { + const roomId: string = await app.client.createRoom({}); + await app.client.inviteUser(roomId, bot.credentials.userId); + await page.goto("/#/room/" + roomId); + + const locator = await app.openMessageComposerOptions(); + await locator.getByRole("menuitem", { name: "Poll" }).click(); + + const pollParams = { + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"], + }; + await createPoll(page, pollParams); + + // Wait for message to send, get its ID and save as @pollId + const pollId = await page + .locator(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]") + .filter({ hasText: pollParams.title }) + .getAttribute("data-scroll-tokens"); + + // Bot votes 'Maybe' in the poll + await botVoteForOption(page, bot, roomId, pollId, pollParams.options[2]); + + // wait for bot's vote to arrive + await expect(page.locator(".mx_MPollBody_totalVotes")).toContainText("1 vote cast"); + + // Open context menu + await getPollTile(page, pollId).click({ button: "right" }); + + // Select edit item + await page.getByRole("menuitem", { name: "Edit" }).click(); + + // Expect poll editing dialog + await expect(page.locator(".mx_ErrorDialog")).toBeAttached(); + }); + + test("should be displayed correctly in thread panel", async ({ page, app, user, bot, homeserver }) => { + const botCharlie = new Bot(page, homeserver, { displayName: "BotCharlie" }); + await botCharlie.prepareClient(); + + const roomId: string = await app.client.createRoom({}); + await app.client.inviteUser(roomId, bot.credentials.userId); + await app.client.inviteUser(roomId, botCharlie.credentials.userId); + await page.goto("/#/room/" + roomId); + + // wait until the bots joined + await expect(page.getByText("BotBob and one other were invited and joined")).toBeAttached({ timeout: 10000 }); + + const locator = await app.openMessageComposerOptions(); + await locator.getByRole("menuitem", { name: "Poll" }).click(); + + const pollParams = { + title: "Does the polls feature work?", + options: ["Yes", "No", "Maybe"], + }; + await createPoll(page, pollParams); + + // Wait for message to send, get its ID and save as @pollId + const pollId = await page + .locator(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]") + .filter({ hasText: pollParams.title }) + .getAttribute("data-scroll-tokens"); + + // Bob starts thread on the poll + await bot.sendMessage( + roomId, + { + body: "Hello there", + msgtype: "m.text", + }, + pollId, + ); + + // open the thread summary + await page.getByRole("button", { name: "Open thread" }).click(); + + // Bob votes 'Maybe' in the poll + await botVoteForOption(page, bot, roomId, pollId, pollParams.options[2]); + + // Charlie votes 'No' + await botVoteForOption(page, botCharlie, roomId, pollId, pollParams.options[1]); + + // no votes shown until I vote, check votes have arrived in main tl + await expect( + page + .locator(".mx_RoomView_body .mx_MPollBody_totalVotes") + .getByText("2 votes cast. Vote to see the results"), + ).toBeAttached(); + + // and thread view + await expect( + page.locator(".mx_ThreadView .mx_MPollBody_totalVotes").getByText("2 votes cast. Vote to see the results"), + ).toBeAttached(); + + // Take snapshots of poll on ThreadView + await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); + await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='bubble']").first()).toBeVisible(); + await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("ThreadView_with_a_poll_on_bubble_layout.png", { + mask: [page.locator(".mx_MessageTimestamp")], + }); + + await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group); + await expect(page.locator(".mx_ThreadView .mx_EventTile[data-layout='group']").first()).toBeVisible(); + + await expect(page.locator(".mx_ThreadView")).toMatchScreenshot("ThreadView_with_a_poll_on_group_layout.png", { + mask: [page.locator(".mx_MessageTimestamp")], + }); + + const roomViewLocator = page.locator(".mx_RoomView_body"); + // vote 'Maybe' in the main timeline poll + await getPollOption(page, pollId, pollParams.options[2], roomViewLocator).click(); + // both me and bob have voted Maybe + await expectPollOptionVoteCount(page, pollId, pollParams.options[2], 2, roomViewLocator); + + const threadViewLocator = page.locator(".mx_ThreadView"); + // votes updated in thread view too + await expectPollOptionVoteCount(page, pollId, pollParams.options[2], 2, threadViewLocator); + // change my vote to 'Yes' + await getPollOption(page, pollId, pollParams.options[0], threadViewLocator).click(); + + // Bob updates vote to 'No' + await botVoteForOption(page, bot, roomId, pollId, pollParams.options[1]); + + // me: yes, bob: no, charlie: no + const expectVoteCounts = async (optLocator: Locator) => { + // I voted yes + await expectPollOptionVoteCount(page, pollId, pollParams.options[0], 1, optLocator); + // Bob and Charlie voted no + await expectPollOptionVoteCount(page, pollId, pollParams.options[1], 2, optLocator); + // 0 for maybe + await expectPollOptionVoteCount(page, pollId, pollParams.options[2], 0, optLocator); + }; + + // check counts are correct in main timeline tile + await expectVoteCounts(page.locator(".mx_RoomView_body")); + + // and in thread view tile + await expectVoteCounts(page.locator(".mx_ThreadView")); + }); +}); diff --git a/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png b/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a7194257898e08787b8bc26052ea47ce000ac20d GIT binary patch literal 16778 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 I`r^(11@|*Bm;e9( literal 0 HcmV?d00001 diff --git a/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png b/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..73deb5b9293d562e96929828165a5ef87a7d4359 GIT binary patch literal 24390 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 z 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 literal 0 HcmV?d00001