Skip to content

Commit

Permalink
test[e2e]: Add PlanX input components to create-flow test (#3617)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamdelion authored Sep 5, 2024
1 parent 4db9b3a commit 7907d0a
Show file tree
Hide file tree
Showing 20 changed files with 477 additions and 262 deletions.
8 changes: 8 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ More info about e2e tests is available in our [testing approach documentation](h

We use [Playwright](https://playwright.dev/docs/api/class-test) to run UI driven tests where user interactions are simulated via a web browser.

### Running UI-driven tests

1. Navigate to `/tests/ui-driven`
2. Run `pnpm install` to install the Playwright package.
3. Run `pnpm exec playwright install` to install the Playwright test browsers.
4. Run the tests with `pnpm test`



## API driven tests

Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/api-driven/src/globalHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TEST_EMAIL } from "../../ui-driven/src/globalHelpers";
import { TEST_EMAIL } from "../../ui-driven/src/helpers/globalHelpers";
import { $admin } from "./client";

export function createTeam(
Expand Down
16 changes: 8 additions & 8 deletions e2e/tests/api-driven/src/invite-to-pay/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { CustomWorld } from "./steps";
import axios from "axios";
import { readFileSync } from "node:fs";
import type {
FlowGraph,
PaymentRequest,
} from "@opensystemslab/planx-core/types";
import axios from "axios";
import gql from "graphql-tag";
import { readFileSync } from "node:fs";
import { TEST_EMAIL } from "../../../ui-driven/src/helpers/globalHelpers";
import { $admin } from "../client";
import { createTeam, createUser } from "../globalHelpers";
import {
inviteToPayFlowGraph,
sendNodeWithDestination,
mockBreadcrumbs,
mockPassport,
sendNodeWithDestination,
} from "./mocks";
import { $admin } from "../client";
import { TEST_EMAIL } from "../../../ui-driven/src/globalHelpers";
import { createTeam, createUser } from "../globalHelpers";
import gql from "graphql-tag";
import { CustomWorld } from "./steps";

export async function setUpMocks() {
const serverMockFile = readFileSync(`${__dirname}/mocks/server-mocks.yaml`);
Expand Down
100 changes: 71 additions & 29 deletions e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { test, expect, Browser } from "@playwright/test";
import { Browser, expect, test } from "@playwright/test";
import {
createAddressInput,
createChecklist,
createContactInput,
createDateInput,
createNotice,
createNumberInput,
createQuestionWithOptions,
createTextInput,
} from "../helpers/addComponent";
import type { Context } from "../helpers/context";
import {
contextDefaults,
setUpTestContext,
tearDownTestContext,
} from "../context";
} from "../helpers/context";
import { getTeamPage } from "../helpers/getPage";
import {
createAuthenticatedSession,
answerQuestion,
clickContinue,
} from "../globalHelpers";
import type { Context } from "../context";
import { getTeamPage, isGetUserRequest } from "./helpers";
isGetUserRequest,
} from "../helpers/globalHelpers";
import { answerQuestion, clickContinue } from "../helpers/userActions";

test.describe("Navigation", () => {
let context: Context = {
Expand Down Expand Up @@ -102,47 +112,79 @@ test.describe("Navigation", () => {
// update context to allow flow to be torn down
context.flow = { ...serviceProps };

await page.locator("li.hanger > a").click();
await page.getByRole("dialog").waitFor();
const firstNode = page.locator("li.hanger > a").first();

const questionText = "Is this a test?";
await page.getByPlaceholder("Text").fill(questionText);

await page.locator("button").filter({ hasText: "add new" }).click();
await page.getByPlaceholder("Option").fill("Yes");

await page.locator("button").filter({ hasText: "add new" }).click();
await page.getByPlaceholder("Option").nth(1).fill("No");

await page.locator("button").filter({ hasText: "Create question" }).click();
await createQuestionWithOptions(page, firstNode, questionText, [
"Yes",
"No",
]);
await expect(
page.locator("a").filter({ hasText: questionText }),
).toBeVisible();

// Add a notice to the "Yes" path
const yesBranch = page.locator("#flow .card .options .option").nth(0);
await yesBranch.locator(".hanger > a").click();
await page.getByRole("dialog").waitFor();

await page.locator("select").selectOption({ label: "Notice" });
const yesBranchNoticeText = "Yes! this is a test";
await page.getByPlaceholder("Notice").fill(yesBranchNoticeText);
await page.locator("button").filter({ hasText: "Create notice" }).click();
await createNotice(
page,
yesBranch.locator(".hanger > a"),
yesBranchNoticeText,
);

// Add a notice to the "No" path
const noBranch = page.locator("#flow .card .options .option").nth(1);
await noBranch.locator(".hanger > a").click();
await page.getByRole("dialog").waitFor();

await page.locator("select").selectOption({ label: "Notice" });
const noBranchNoticeText = "Sorry, this is a test";
await page.getByPlaceholder("Notice").fill(noBranchNoticeText);
await page.locator("button").filter({ hasText: "Create notice" }).click();
await createNotice(
page,
noBranch.locator(".hanger > a"),
noBranchNoticeText,
);

// TODO: find a nicer way to find the next node
let nextNode = page.locator(".hanger > a").nth(5);
await createChecklist(page, nextNode, "A checklist title", [
"Checklist item 1",
"Second checklist item",
"The third checklist item",
]);

nextNode = page.locator(".hanger > a").nth(7);
await createTextInput(page, nextNode, "Tell us about your trees.");

nextNode = page.locator(".hanger > a").nth(8);
await createNumberInput(page, nextNode, "How old are you?", "years");

nextNode = page.locator(".hanger > a").nth(9);
await createDateInput(page, nextNode, "When is your birthday?");

nextNode = page.locator(".hanger > a").nth(10);
await createAddressInput(
page,
nextNode,
"What is your address?",
"some data field",
);

nextNode = page.locator(".hanger > a").nth(11);
await createContactInput(
page,
nextNode,
"What is your contact info?",
"some data field",
);

const nodes = page.locator(".card");
await expect(nodes.getByText(questionText)).toBeVisible();
await expect(nodes.getByText(yesBranchNoticeText)).toBeVisible();
await expect(nodes.getByText(noBranchNoticeText)).toBeVisible();
await expect(nodes.getByText("Checklist item 1")).toBeVisible();
await expect(nodes.getByText("Tell us about your trees.")).toBeVisible();
await expect(nodes.getByText("How old are you?")).toBeVisible();
await expect(nodes.getByText("When is your birthday?")).toBeVisible();
await expect(nodes.getByText("What is your address?")).toBeVisible();
await expect(nodes.getByText("What is your contact info?")).toBeVisible();
});

test("Cannot preview an unpublished flow", async ({
Expand Down
160 changes: 160 additions & 0 deletions e2e/tests/ui-driven/src/helpers/addComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { Locator, Page } from "@playwright/test";

const createBaseInput = async (
page: Page,
locatingNode: Locator,
type: string,
title?: string,
options?: string[],
) => {
await locatingNode.click();
await page.getByRole("dialog").waitFor();
await page.locator("select").selectOption({ label: type });

switch (type) {
case "Question":
await page.getByPlaceholder("Text").fill(title || "");
if (options) {
await createOptions(options, "add new", page);
}
break;
case "Notice":
await page.getByPlaceholder("Notice").fill(title || "");
break;
case "Checklist":
await page.getByPlaceholder("Text").fill(title || "");
if (options) {
await createOptions(options, "add new option", page);
}
break;
case "Text Input":
await page.getByPlaceholder("Title").fill(title || "");
break;
case "Number Input":
await page.getByPlaceholder("Title").fill(title || "");
await page.getByPlaceholder("eg square metres").fill(options?.[0] || "");
break;
case "Date Input":
await page.getByPlaceholder("Title").fill(title || "");
// fill with hardcoded dates for now
await page.locator("id=undefined-min-day").fill("01");
await page.locator("id=undefined-min-month").fill("01");
await page.locator("id=undefined-min-year").fill("1800");
await page.locator("id=undefined-max-day").fill("31");
await page.locator("id=undefined-max-month").fill("12");
await page.locator("id=undefined-max-year").fill("2199");
break;
case "Address Input":
await page.getByPlaceholder("Title").fill(title || "");
await page.getByPlaceholder("Data Field").fill(options?.[0] || "");
break;
case "Contact Input":
await page.getByPlaceholder("Title").fill(title || "");
await page.getByPlaceholder("Data Field").fill(options?.[0] || "");
break;
default:
throw new Error(`Unsupported type: ${type}`);
}

// convert type name to lowercase, with dashes if there are spaces
const buttonName = type.toLowerCase().replace(/\s/g, "-");
await page
.locator("button")
.filter({
hasText: `Create ${buttonName}`,
})
.click();
};

export const createQuestionWithOptions = async (
page: Page,
locatingNode: Locator,
questionText: string,
options: string[],
) => {
await createBaseInput(page, locatingNode, "Question", questionText, options);
};

export const createNotice = async (
page: Page,
locatingNode: Locator,
noticeText: string,
) => {
await createBaseInput(page, locatingNode, "Notice", noticeText);
};

export const createChecklist = async (
page: Page,
locatingNode: Locator,
checklistTitle: string,
checklistOptions: string[],
) => {
createBaseInput(
page,
locatingNode,
"Checklist",
checklistTitle,
checklistOptions,
);
};

export const createTextInput = async (
page: Page,
locatingNode: Locator,
inputTitle: string,
) => {
await createBaseInput(page, locatingNode, "Text Input", inputTitle);
};

export const createNumberInput = async (
page: Page,
locatingNode: Locator,
inputTitle: string,
inputUnits: string,
) => {
await createBaseInput(page, locatingNode, "Number Input", inputTitle, [
inputUnits,
]);
};

export const createDateInput = async (
page: Page,
locatingNode: Locator,
inputTitle: string,
) => {
await createBaseInput(page, locatingNode, "Date Input", inputTitle);
};

export const createAddressInput = async (
page: Page,
locatingNode: Locator,
inputTitle: string,
inputDataField: string,
) => {
await createBaseInput(page, locatingNode, "Address Input", inputTitle, [
inputDataField,
]);
};

export const createContactInput = async (
page: Page,
locatingNode: Locator,
inputTitle: string,
inputDataField: string,
) => {
await createBaseInput(page, locatingNode, "Contact Input", inputTitle, [
inputDataField,
]);
};
async function createOptions(
options: string[],
buttonText: string,
page: Page,
) {
let index = 0;
for (const option of options) {
await page.locator("button").filter({ hasText: buttonText }).click();
await page.getByPlaceholder("Option").nth(index).fill(option);
index++;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import assert from "node:assert";
import { log } from "./globalHelpers";
import { sign } from "jsonwebtoken";
import { CoreDomainClient } from "@opensystemslab/planx-core";
import { GraphQLClient, gql } from "graphql-request";
import { sign } from "jsonwebtoken";
import assert from "node:assert";
import { log } from "./globalHelpers";

type NewTeam = Parameters<CoreDomainClient["team"]["create"]>[0];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { Browser, Page, Request } from "@playwright/test";
import { createAuthenticatedSession } from "../globalHelpers";

export const isGetUserRequest = (req: Request) =>
req.url().includes("/user/me");
import { Browser, Page } from "@playwright/test";
import { createAuthenticatedSession } from "./globalHelpers";

export async function getAdminPage({
browser,
Expand Down
Loading

0 comments on commit 7907d0a

Please sign in to comment.