diff --git a/cypress/e2e/polls/pollHistory.spec.ts b/cypress/e2e/polls/pollHistory.spec.ts
new file mode 100644
index 0000000000..2b9a0b6734
--- /dev/null
+++ b/cypress/e2e/polls/pollHistory.spec.ts
@@ -0,0 +1,189 @@
+/*
+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.get('.mx_HeaderButtons [aria-label="Room info"]').click();
+ cy.get(".mx_RoomSummaryCard").within(() => {
+ cy.contains("Polls 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.enableLabsFeature("feature_poll_history");
+
+ 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.contains(".mx_TextualEvent", "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.contains(pollParams1.title).click();
+
+ // vote in the poll
+ cy.contains("Yes").click();
+ cy.get('[data-testid="totalVotes"]').should("have.text", "Based on 2 votes");
+
+ // navigate back to list
+ cy.contains("Active polls").click();
+
+ // go to past polls list
+ cy.contains("Past polls").click();
+
+ cy.contains(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.contains(pollParams2.title).should("exist");
+ cy.contains(pollParams1.title).should("exist");
+
+ cy.contains("Active polls").click();
+
+ // no more active polls
+ cy.contains("There are no active polls in this room").should("exist");
+ });
+ });
+});