From eff447d83fff631c0d20516f0c5b66ab15298f1d Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Fri, 22 Nov 2024 14:20:48 +0000 Subject: [PATCH 01/17] add test setup and refinment --- e2e/tests/ui-driven/playwright.config.ts | 2 +- .../src/pages/FlowEditor/components/forms/FormModal.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/tests/ui-driven/playwright.config.ts b/e2e/tests/ui-driven/playwright.config.ts index 349006dbe1..2fe1fae19a 100644 --- a/e2e/tests/ui-driven/playwright.config.ts +++ b/e2e/tests/ui-driven/playwright.config.ts @@ -14,7 +14,7 @@ dotenv.config({ path: "../../../.env" }); const config: PlaywrightTestConfig = { testDir: "src", /* Maximum time one test can run for. */ - timeout: 45 * 1000, + timeout: 100 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. diff --git a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx index af7400b806..331bce10c4 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx @@ -125,6 +125,7 @@ const FormModal: React.FC<{ Date: Mon, 25 Nov 2024 11:12:37 +0000 Subject: [PATCH 02/17] refine invite to pay test --- e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts index 23d94b2609..ca7301db5f 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts @@ -20,6 +20,7 @@ import { navigateToPayComponent, } from "./helpers"; import { mockPaymentRequest, modifiedInviteToPayFlow } from "./mocks"; +import exp from "node:constants"; let context: Context = { ...contextDefaults, @@ -118,12 +119,12 @@ test.describe("Agent journey @regression", async () => { await secondPage.getByLabel("Email address").fill(context.user.email); await secondPage.getByTestId("continue-button").click(); - await expect( - secondPage.getByRole("heading", { - name: "Sorry, you can't make changes to this application", - }), - ).toBeVisible(); - await expect(secondPage.getByTestId("continue-button")).toBeHidden(); + const errorHeader = secondPage.getByRole("heading", { + name: "Sorry, you can't make changes to this application", + }) + + await expect(errorHeader).toBeVisible() + await expect(secondPage.getByTestId("continue-button")).toBeHidden() }); test("reconciliation does not apply to sessions with open payment requests", async ({ From 6519d3ac1fb1901c52248f7759423db3118923dd Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Mon, 25 Nov 2024 11:13:28 +0000 Subject: [PATCH 03/17] remove unused import --- e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts index ca7301db5f..1fb4d2a3d4 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts @@ -20,7 +20,6 @@ import { navigateToPayComponent, } from "./helpers"; import { mockPaymentRequest, modifiedInviteToPayFlow } from "./mocks"; -import exp from "node:constants"; let context: Context = { ...contextDefaults, From e779fbd0611022478424e7ff1203ccfeca24379b Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Mon, 25 Nov 2024 12:45:53 +0000 Subject: [PATCH 04/17] add setup for multiple flows to be made at same time --- .../src/create-flow-with-geospatial.spec.ts | 2 +- e2e/tests/ui-driven/src/create-flow.spec.ts | 35 ++++++++- e2e/tests/ui-driven/src/helpers/context.ts | 73 +++++++++++-------- .../ui-driven/src/helpers/globalHelpers.ts | 4 +- .../ui-driven/src/helpers/userActions.ts | 2 +- .../ui-driven/src/invite-to-pay/agent.spec.ts | 10 +-- .../ui-driven/src/invite-to-pay/helpers.ts | 2 +- .../src/invite-to-pay/nominee.spec.ts | 14 ++-- e2e/tests/ui-driven/src/pay.spec.ts | 6 +- .../ui-driven/src/save-and-return.spec.ts | 6 +- e2e/tests/ui-driven/src/sections.spec.ts | 10 +-- 11 files changed, 102 insertions(+), 62 deletions(-) diff --git a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts index 991dcd0bc5..5a41195bfa 100644 --- a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts @@ -45,7 +45,7 @@ test.describe("Flow creation, publish and preview", () => { await editor.addNewService(); // update context to allow flow to be torn down - context.flow = { ...serviceProps }; + context.flows = [{ ...serviceProps }]; await editor.createFindProperty(); await expect(editor.nodeList).toContainText(["Find property"]); diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index 94c6bee879..67ff04f3ea 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -29,6 +29,11 @@ test.describe("Flow creation, publish and preview", () => { slug: "a-test-service", }; + const externalPortalServiceProps = { + name: "An External Portal Service", + slug: "an-external-portal-service", + }; + test.beforeAll(async () => { try { context = await setUpTestContext(context); @@ -56,7 +61,7 @@ test.describe("Flow creation, publish and preview", () => { await editor.addNewService(); // update context to allow flow to be torn down - context.flow = { ...serviceProps }; + context.flows = [{ ...serviceProps }]; await editor.createQuestion(); await editor.createNoticeOnEachBranch(); @@ -177,6 +182,34 @@ test.describe("Flow creation, publish and preview", () => { await expect(previewLink).toBeVisible(); }); + test("Can add an external portal", async ({browser}:{ + browser: Browser; + }) => { + const page = await createAuthenticatedSession({ + browser, + userId: context.user!.id!, + }); + + await page.goto( + `/${context.team.slug}`, + ); + + const editor = new PlaywrightEditor(page); + + page.on("dialog", (dialog) => dialog.accept(externalPortalServiceProps.name)); + await editor.addNewService(); + + // update context to allow flow to be torn down + context.flows?.push({...externalPortalServiceProps}) + + await editor.createQuestion(); + await expect(editor.nodeList).toContainText([ + "Is this a test?", + "Yes", + "No", + ]); + }); + test("Can preview a published flow", async ({ browser, }: { diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index c94783189e..577501d4be 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -6,6 +6,14 @@ import { log } from "./globalHelpers"; type NewTeam = Parameters[0]; +export interface Flow { + id?: string; + publishedId?: number; + slug: string; + name: string; + data?: object; +} + export interface Context { user: { id?: number; @@ -15,13 +23,7 @@ export interface Context { isPlatformAdmin: boolean; }; team: { id?: number } & NewTeam; - flow?: { - id?: string; - publishedId?: number; - slug: string; - name: string; - data?: object; - }; + flows?: Flow[]; sessionIds?: string[]; } @@ -65,23 +67,24 @@ export async function setUpTestContext( }); } if ( - context.flow?.slug && - context.flow?.data && - context.flow?.name && + context.flows && + context.flows![0].slug && + context.flows![0].data && + context.flows![0].name && context.team?.id && context.user?.id ) { - context.flow.id = await $admin.flow.create({ - slug: context.flow.slug, - name: context.flow.name, + context.flows![0].id = await $admin.flow.create({ + slug: context.flows![0].slug, + name: context.flows![0].name, teamId: context.team.id, - data: context.flow!.data!, + data: context.flows![0]!.data!, status: "online", }); - context.flow.publishedId = await $admin.flow.publish({ + context.flows![0].publishedId = await $admin.flow.publish({ flow: { - id: context.flow.id, - data: context.flow!.data!, + id: context.flows![0].id, + data: context.flows![0]!.data!, }, publisherId: context.user!.id!, }); @@ -93,10 +96,15 @@ export async function setUpTestContext( export async function tearDownTestContext(context: Context) { const adminGQLClient = getGraphQLClient(); - if (context.flow) { + if (context.flows && context.flows[0]) { await deleteSession(adminGQLClient, context); - await deletePublishedFlow(adminGQLClient, context); - await deleteFlow(adminGQLClient, context); + + await Promise.all(context.flows.map(async (flow) => { + await deleteFlow(adminGQLClient, flow); + if(flow.publishedId){ + await deletePublishedFlow(adminGQLClient, flow); + } + })) } if (context.user) { await deleteUser(adminGQLClient, context); @@ -152,7 +160,7 @@ export async function findSessionId( id } }`, - { slug: context.flow?.slug }, + { slug: context.flows![0].slug }, ); if (!flowResponse.flows.length || !flowResponse.flows[0].id) { return; @@ -208,33 +216,33 @@ async function deleteSession(adminGQLClient: GraphQLClient, context) { async function deletePublishedFlow( adminGQLClient: GraphQLClient, - context: Context, + flow: Flow, ) { - if (context.flow?.publishedId) { - log(`deleting published flow ${context.flow?.publishedId}`); + if (flow?.publishedId) { + log(`deleting published flow ${flow?.publishedId}`); await adminGQLClient.request( `mutation DeleteTestPublishedFlow( $publishedFlowId: Int!) { delete_published_flows_by_pk(id: $publishedFlowId) { id } }`, - { publishedFlowId: context.flow?.publishedId }, + { publishedFlowId: flow?.publishedId }, ); } } -async function deleteFlow(adminGQLClient: GraphQLClient, context: Context) { - if (context.flow?.id) { - log(`deleting flow ${context.flow?.id}`); +async function deleteFlow(adminGQLClient: GraphQLClient, flow: Flow ) { + if (flow?.id) { + log(`deleting flow ${flow?.id}`); await adminGQLClient.request( `mutation DeleteTestFlow($flowId: uuid!) { delete_flows_by_pk(id: $flowId) { id } }`, - { flowId: context.flow?.id }, + { flowId: flow?.id }, ); - } else if (context.flow?.slug) { + } else if (flow?.slug) { // try deleting via slug (when cleaning up from a previously failed test) const response: { flows: { id: string }[] } = await adminGQLClient.request( `query GetFlowBySlug($slug: String!) { @@ -242,11 +250,11 @@ async function deleteFlow(adminGQLClient: GraphQLClient, context: Context) { id } }`, - { slug: context.flow?.slug }, + { slug: flow?.slug }, ); if (response.flows.length && response.flows[0].id) { log( - `deleting flow ${context.flow?.slug} flowId: ${response.flows[0].id}`, + `deleting flow ${flow?.slug} flowId: ${response.flows[0].id}`, ); await adminGQLClient.request( `mutation DeleteTestFlow( $flowId: uuid!) { @@ -261,6 +269,7 @@ async function deleteFlow(adminGQLClient: GraphQLClient, context: Context) { } async function deleteUser(adminGQLClient: GraphQLClient, context: Context) { + console.log(context); if (context.user?.id) { log(`deleting user ${context.user?.id}`); await adminGQLClient.request( diff --git a/e2e/tests/ui-driven/src/helpers/globalHelpers.ts b/e2e/tests/ui-driven/src/helpers/globalHelpers.ts index 29e00f7e64..a40dc8081c 100644 --- a/e2e/tests/ui-driven/src/helpers/globalHelpers.ts +++ b/e2e/tests/ui-driven/src/helpers/globalHelpers.ts @@ -114,7 +114,7 @@ export async function modifyFlow({ modifiedFlow: FlowGraph; }) { const adminGQLClient = getGraphQLClient(); - if (!context.flow?.id || !context.user?.id) { + if (!context.flows![0].id || !context.user?.id) { throw new Error("context must have a flow and user"); } await adminGQLClient.request( @@ -132,7 +132,7 @@ export async function modifyFlow({ } `, { - flowId: context.flow!.id, + flowId: context.flows![0].id, userId: context.user!.id, data: modifiedFlow, }, diff --git a/e2e/tests/ui-driven/src/helpers/userActions.ts b/e2e/tests/ui-driven/src/helpers/userActions.ts index 7b2c92709a..f0fc59b317 100644 --- a/e2e/tests/ui-driven/src/helpers/userActions.ts +++ b/e2e/tests/ui-driven/src/helpers/userActions.ts @@ -35,7 +35,7 @@ export async function returnToSession({ sessionId: string; shouldContinue?: boolean; }) { - const returnURL = `/${context.team?.slug}/${context.flow?.slug}/published?analytics=false&sessionId=${sessionId}`; + const returnURL = `/${context.team?.slug}/${context.flows![0].slug}/published?analytics=false&sessionId=${sessionId}`; log(`returning to http://localhost:3000/${returnURL}`); await page.goto(returnURL, { waitUntil: "load" }); await page.locator("#email").fill(context.user?.email); diff --git a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts index 1fb4d2a3d4..2523b61e6e 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts @@ -23,11 +23,11 @@ import { mockPaymentRequest, modifiedInviteToPayFlow } from "./mocks"; let context: Context = { ...contextDefaults, - flow: { + flows:[ { slug: "invite-to-pay-test", name: "Invite to pay test", data: inviteToPayFlow, - }, + }], sessionIds: [], // used to collect and clean up sessions }; @@ -106,8 +106,7 @@ test.describe("Agent journey @regression", async () => { const sessionId = await makePaymentRequest({ page: firstPage, context }); // Resume session - const resumeLink = `/${context.team!.slug!}/${context.flow! - .slug!}/published?analytics=false&sessionId=${sessionId}`; + const resumeLink = `/${context.team!.slug!}/${context.flows![0].slug}/published?analytics=false&sessionId=${sessionId}`; const secondPage = await browserContext.newPage(); await secondPage.goto(resumeLink); await expect( @@ -136,8 +135,7 @@ test.describe("Agent journey @regression", async () => { await modifyFlow({ context, modifiedFlow: modifiedInviteToPayFlow }); // Navigate to resume session link - const resumeLink = `/${context.team!.slug!}/${context.flow! - .slug!}/published?analytics=false&sessionId=${sessionId}`; + const resumeLink = `/${context.team!.slug!}/${context.flows![0].slug}/published?analytics=false&sessionId=${sessionId}`; const secondPage = await browserContext.newPage(); await secondPage.goto(resumeLink); await expect( diff --git a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts index 10347d7a81..ebaae1bc92 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts @@ -14,7 +14,7 @@ import { * Navigates to pay component whilst completing the minimum requirements for an Invite to Pay flow */ export async function navigateToPayComponent(page: Page, context: Context) { - const previewURL = `/${context.team!.slug!}/${context.flow! + const previewURL = `/${context.team!.slug!}/${context.flows![0] .slug!}/published?analytics=false`; await page.goto(previewURL); diff --git a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts index bd78120699..9afbda7ce9 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts @@ -17,11 +17,11 @@ import { mockPaymentRequestDetails, mockSessionData } from "./mocks"; let context: Context = { ...contextDefaults, - flow: { + flows: [{ slug: "invite-to-pay-test", name: "Invite to pay test", data: inviteToPayFlow, - }, + }], sessionIds: [], // used to collect and clean up sessions }; @@ -82,7 +82,7 @@ test.describe("Nominee journey @regression", async () => { }); test("navigating to a URL with an invalid ID", async ({ page }) => { - const invalidPaymentRequestURL = `/${context.team!.slug!}/${context.flow! + const invalidPaymentRequestURL = `/${context.team!.slug!}/${context.flows![0] .slug!}/pay?analytics=false&paymentRequestId=INVALID-ID`; await page.goto(invalidPaymentRequestURL); await page.waitForLoadState("networkidle"); @@ -91,7 +91,7 @@ test.describe("Nominee journey @regression", async () => { }); test("navigating to a URL without a paymentRequestId", async ({ page }) => { - const invalidPaymentRequestURL = `/${context.team!.slug!}/${context.flow! + const invalidPaymentRequestURL = `/${context.team!.slug!}/${context.flows![0] .slug!}/pay?analytics=false`; await page.goto(invalidPaymentRequestURL); await page.waitForLoadState("networkidle"); @@ -126,7 +126,7 @@ async function navigateToPaymentRequestPage( paymentRequest: PaymentRequest, page: Page, ) { - const paymentRequestURL = `/${context.team!.slug!}/${context.flow! + const paymentRequestURL = `/${context.team!.slug!}/${context.flows![0] .slug!}/pay?analytics=false&paymentRequestId=${paymentRequest.id}`; await page.goto(paymentRequestURL); await page.waitForLoadState("networkidle"); @@ -171,11 +171,11 @@ async function createSession({ await client.request[]>>(mutation, { id: sessionId, data: { - id: context.flow?.id, + id: context.flows![0].id, ...mockSessionData, }, email: context.user.email, - flowId: context.flow?.id, + flowId: context.flows![0].id, }); } diff --git a/e2e/tests/ui-driven/src/pay.spec.ts b/e2e/tests/ui-driven/src/pay.spec.ts index edf63ffdb1..1275bf29a9 100644 --- a/e2e/tests/ui-driven/src/pay.spec.ts +++ b/e2e/tests/ui-driven/src/pay.spec.ts @@ -20,14 +20,14 @@ import payFlow from "./mocks/flows/pay-flow.json"; let context: Context = { ...contextDefaults, - flow: { + flows:[ { slug: "pay-test", name: "Pay test", data: payFlow, - }, + }], sessionIds: [], // used to collect and clean up sessions }; -const previewURL = `/${context.team!.slug!}/${context.flow! +const previewURL = `/${context.team!.slug!}/${context.flows![0] .slug!}/published?analytics=false`; const payButtonText = "Pay now using GOV.UK Pay"; diff --git a/e2e/tests/ui-driven/src/save-and-return.spec.ts b/e2e/tests/ui-driven/src/save-and-return.spec.ts index c2d01b954e..be532a2a6c 100644 --- a/e2e/tests/ui-driven/src/save-and-return.spec.ts +++ b/e2e/tests/ui-driven/src/save-and-return.spec.ts @@ -22,13 +22,13 @@ import { test.describe("Save and return", () => { let context: Context = { ...contextDefaults, - flow: { + flows: [{ slug: "e2e-save-and-return-test-flow", name: "E2E Save and Return test flow", data: simpleSendFlow, - }, + }], }; - const previewURL = `/${context.team?.slug}/${context.flow?.slug}/published?analytics=false`; + const previewURL = `/${context.team?.slug}/${context.flows![0].slug}/published?analytics=false`; test.beforeAll(async () => { try { diff --git a/e2e/tests/ui-driven/src/sections.spec.ts b/e2e/tests/ui-driven/src/sections.spec.ts index 0aca98272b..d9648e5b12 100644 --- a/e2e/tests/ui-driven/src/sections.spec.ts +++ b/e2e/tests/ui-driven/src/sections.spec.ts @@ -36,11 +36,11 @@ export enum SectionStatus { test.describe("Section statuses", () => { let context: Context = { ...contextDefaults, - flow: { + flows:[ { slug: "sections-test-flow", name: "Sections test flow", data: flow, - }, + }], }; test.beforeAll(async () => { @@ -53,7 +53,7 @@ test.describe("Section statuses", () => { }); test.beforeEach(async ({ page }) => { - const previewURL = `/${context.team?.slug}/${context.flow?.slug}/published?analytics=false`; + const previewURL = `/${context.team?.slug}/${context.flows![0].slug}/published?analytics=false`; await page.goto(previewURL); }); @@ -535,7 +535,7 @@ async function modifyFlow({ flowData: FlowGraph; }) { const adminGQLClient = getGraphQLClient(); - if (!context.flow?.id || !context.user?.id) { + if (!context.flows![0].id || !context.user?.id) { throw new Error("context must have a flow and user"); } await adminGQLClient.request( @@ -553,7 +553,7 @@ async function modifyFlow({ } `, { - flowId: context.flow!.id, + flowId: context.flows![0].id, userId: context.user!.id, data: flowData, }, From 23d5291ee93961ed046bb2f2ea7f72a65e430caa Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Mon, 25 Nov 2024 15:00:05 +0000 Subject: [PATCH 05/17] use testId in modal select --- .../src/pages/FlowEditor/components/forms/FormModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx index 331bce10c4..af7400b806 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx @@ -125,7 +125,6 @@ const FormModal: React.FC<{ Date: Mon, 25 Nov 2024 16:04:27 +0000 Subject: [PATCH 06/17] add working external portal test --- e2e/tests/ui-driven/src/create-flow.spec.ts | 54 +++++++++++++++---- .../ui-driven/src/helpers/addComponent.ts | 15 ++++++ e2e/tests/ui-driven/src/helpers/context.ts | 18 +++++-- e2e/tests/ui-driven/src/pages/Editor.ts | 8 +++ 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index 67ff04f3ea..1394dfc57b 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -2,6 +2,8 @@ import { Browser, expect, test } from "@playwright/test"; import type { Context } from "./helpers/context"; import { contextDefaults, + externalPortalFlowData, + externalPortalServiceProps, setUpTestContext, tearDownTestContext, } from "./helpers/context"; @@ -19,6 +21,7 @@ import { clickContinue, } from "./helpers/userActions"; import { PlaywrightEditor } from "./pages/Editor"; +import { createExternalPortal, createQuestionWithOptions } from "./helpers/addComponent"; test.describe("Flow creation, publish and preview", () => { let context: Context = { @@ -29,11 +32,6 @@ test.describe("Flow creation, publish and preview", () => { slug: "a-test-service", }; - const externalPortalServiceProps = { - name: "An External Portal Service", - slug: "an-external-portal-service", - }; - test.beforeAll(async () => { try { context = await setUpTestContext(context); @@ -202,12 +200,41 @@ test.describe("Flow creation, publish and preview", () => { // update context to allow flow to be torn down context.flows?.push({...externalPortalServiceProps}) - await editor.createQuestion(); + await createQuestionWithOptions(page, editor.firstNode, externalPortalFlowData.title, externalPortalFlowData.answers ) await expect(editor.nodeList).toContainText([ - "Is this a test?", - "Yes", - "No", + externalPortalFlowData.title, + externalPortalFlowData.answers[0], + externalPortalFlowData.answers[1], ]); + + page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); + await expect(page.getByRole('heading', { name: 'Check for changes to publish' })).toBeVisible() + page.getByRole("button", { name: "PUBLISH", exact: true }).click(); + + // Open flow settings + page.locator('[aria-label="Service settings"]').click(); + + // Toggle flow online + page.getByLabel("Offline").click(); + page.getByRole("button", { name: "Save", disabled: false }).click(); + await expect( + page.getByText("Service settings updated successfully"), + ).toBeVisible(); + + await page.goto( + `/${context.team.slug}/${serviceProps.slug}`, + ); + + await expect(page.getByRole('link', { name: serviceProps.slug })).toBeVisible() + + await createExternalPortal(page, page.locator('li:nth-child(6)')) + + await expect(page.getByRole('link', { name: 'E2E/an-external-portal-service' })).toBeVisible() + + page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); + await expect(page.getByRole('heading', { name: 'Check for changes to publish' })).toBeVisible() + page.getByRole("button", { name: "PUBLISH", exact: true }).click(); + }); test("Can preview a published flow", async ({ @@ -220,6 +247,12 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); + await page.goto( + `/${context.team.slug}/${serviceProps.slug}`, + ); + + await expect(page.getByRole('link', { name: 'E2E/an-external-portal-service' })).toBeVisible() + await page.goto( `/${context.team.slug}/${serviceProps.slug}/published?analytics=false`, ); @@ -246,6 +279,9 @@ test.describe("Flow creation, publish and preview", () => { }); await clickContinue({ page }); + await answerQuestion({ page, title: externalPortalFlowData.title, answer: externalPortalFlowData.answers[0] }); + await clickContinue({ page }); + await answerTextInput(page, { expectedQuestion: "Tell us about your trees.", answer: "My trees are lovely", diff --git a/e2e/tests/ui-driven/src/helpers/addComponent.ts b/e2e/tests/ui-driven/src/helpers/addComponent.ts index 003567d4db..9e4e09fadc 100644 --- a/e2e/tests/ui-driven/src/helpers/addComponent.ts +++ b/e2e/tests/ui-driven/src/helpers/addComponent.ts @@ -1,5 +1,6 @@ import { ComponentType } from "@opensystemslab/planx-core/types"; import { expect, Locator, Page } from "@playwright/test"; +import { contextDefaults, externalPortalServiceProps } from "./context"; const createBaseComponent = async ( page: Page, @@ -122,6 +123,9 @@ const createBaseComponent = async ( case ComponentType.InternalPortal: await page.getByPlaceholder("Portal name").fill(title || ""); break; + case ComponentType.ExternalPortal: + await page.getByTestId('flowId').selectOption(`${contextDefaults.team.slug}/${externalPortalServiceProps.slug}`); + break; default: throw new Error(`Unsupported type: ${type}`); } @@ -394,3 +398,14 @@ export const createInternalPortal = async ( export const createFeedback = async (page: Page, locatingNode: Locator) => { await createBaseComponent(page, locatingNode, ComponentType.Feedback); }; + +export const createExternalPortal = async ( + page: Page, + locatingNode: Locator, +) => { + await createBaseComponent( + page, + locatingNode, + ComponentType.ExternalPortal, + ); +}; diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index 577501d4be..0f808d9c5f 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -94,17 +94,28 @@ export async function setUpTestContext( return context; } +export const externalPortalServiceProps = { + name: "An External Portal Service", + slug: "an-external-portal-service", +}; + +export const externalPortalFlowData = { + title: "Is this an External Portal?", +answers:[ "It is an external portal", + "No it is not an External Portal",] +}; + export async function tearDownTestContext(context: Context) { const adminGQLClient = getGraphQLClient(); if (context.flows && context.flows[0]) { await deleteSession(adminGQLClient, context); - await Promise.all(context.flows.map(async (flow) => { + for (const flow of context.flows) { await deleteFlow(adminGQLClient, flow); - if(flow.publishedId){ + if (flow.publishedId) { await deletePublishedFlow(adminGQLClient, flow); } - })) + } } if (context.user) { await deleteUser(adminGQLClient, context); @@ -269,7 +280,6 @@ async function deleteFlow(adminGQLClient: GraphQLClient, flow: Flow ) { } async function deleteUser(adminGQLClient: GraphQLClient, context: Context) { - console.log(context); if (context.user?.id) { log(`deleting user ${context.user?.id}`); await adminGQLClient.request( diff --git a/e2e/tests/ui-driven/src/pages/Editor.ts b/e2e/tests/ui-driven/src/pages/Editor.ts index bb581c1386..a600f24667 100644 --- a/e2e/tests/ui-driven/src/pages/Editor.ts +++ b/e2e/tests/ui-driven/src/pages/Editor.ts @@ -9,6 +9,7 @@ import { createContent, createDateInput, createDrawBoundary, + createExternalPortal, createFeedback, createFileUpload, createFilter, @@ -241,6 +242,13 @@ export class PlaywrightEditor { .fill("A notice inside a portal!"); await this.page.locator('button[form="modal"][type="submit"]').click(); } + + async createExternalPortal() { + await createExternalPortal( + this.page, + this.getNextNode(), + ); + } async createFeedback() { await createFeedback(this.page, this.getNextNode()); From 8cb1cbf792738988d664315ae474da7101f63c8e Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Tue, 26 Nov 2024 09:22:36 +0000 Subject: [PATCH 07/17] lint:fix --- e2e/tests/ui-driven/src/create-flow.spec.ts | 65 ++++++++++++------- .../ui-driven/src/helpers/addComponent.ts | 16 ++--- e2e/tests/ui-driven/src/helpers/context.ts | 14 ++-- .../ui-driven/src/invite-to-pay/agent.spec.ts | 18 ++--- .../src/invite-to-pay/nominee.spec.ts | 20 +++--- e2e/tests/ui-driven/src/pages/Editor.ts | 7 +- e2e/tests/ui-driven/src/pay.spec.ts | 12 ++-- .../ui-driven/src/save-and-return.spec.ts | 12 ++-- e2e/tests/ui-driven/src/sections.spec.ts | 12 ++-- 9 files changed, 98 insertions(+), 78 deletions(-) diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index 1394dfc57b..b8700f90eb 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -21,7 +21,10 @@ import { clickContinue, } from "./helpers/userActions"; import { PlaywrightEditor } from "./pages/Editor"; -import { createExternalPortal, createQuestionWithOptions } from "./helpers/addComponent"; +import { + createExternalPortal, + createQuestionWithOptions, +} from "./helpers/addComponent"; test.describe("Flow creation, publish and preview", () => { let context: Context = { @@ -180,7 +183,9 @@ test.describe("Flow creation, publish and preview", () => { await expect(previewLink).toBeVisible(); }); - test("Can add an external portal", async ({browser}:{ + test("Can add an external portal", async ({ + browser, + }: { browser: Browser; }) => { const page = await createAuthenticatedSession({ @@ -188,27 +193,34 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); - await page.goto( - `/${context.team.slug}`, - ); + await page.goto(`/${context.team.slug}`); const editor = new PlaywrightEditor(page); - page.on("dialog", (dialog) => dialog.accept(externalPortalServiceProps.name)); + page.on("dialog", (dialog) => + dialog.accept(externalPortalServiceProps.name), + ); await editor.addNewService(); // update context to allow flow to be torn down - context.flows?.push({...externalPortalServiceProps}) + context.flows?.push({ ...externalPortalServiceProps }); - await createQuestionWithOptions(page, editor.firstNode, externalPortalFlowData.title, externalPortalFlowData.answers ) + await createQuestionWithOptions( + page, + editor.firstNode, + externalPortalFlowData.title, + externalPortalFlowData.answers, + ); await expect(editor.nodeList).toContainText([ externalPortalFlowData.title, - externalPortalFlowData.answers[0], + externalPortalFlowData.answers[0], externalPortalFlowData.answers[1], ]); page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); - await expect(page.getByRole('heading', { name: 'Check for changes to publish' })).toBeVisible() + await expect( + page.getByRole("heading", { name: "Check for changes to publish" }), + ).toBeVisible(); page.getByRole("button", { name: "PUBLISH", exact: true }).click(); // Open flow settings @@ -221,20 +233,23 @@ test.describe("Flow creation, publish and preview", () => { page.getByText("Service settings updated successfully"), ).toBeVisible(); - await page.goto( - `/${context.team.slug}/${serviceProps.slug}`, - ); + await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - await expect(page.getByRole('link', { name: serviceProps.slug })).toBeVisible() + await expect( + page.getByRole("link", { name: serviceProps.slug }), + ).toBeVisible(); - await createExternalPortal(page, page.locator('li:nth-child(6)')) + await createExternalPortal(page, page.locator("li:nth-child(6)")); - await expect(page.getByRole('link', { name: 'E2E/an-external-portal-service' })).toBeVisible() + await expect( + page.getByRole("link", { name: "E2E/an-external-portal-service" }), + ).toBeVisible(); page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); - await expect(page.getByRole('heading', { name: 'Check for changes to publish' })).toBeVisible() + await expect( + page.getByRole("heading", { name: "Check for changes to publish" }), + ).toBeVisible(); page.getByRole("button", { name: "PUBLISH", exact: true }).click(); - }); test("Can preview a published flow", async ({ @@ -247,11 +262,11 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); - await page.goto( - `/${context.team.slug}/${serviceProps.slug}`, - ); + await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - await expect(page.getByRole('link', { name: 'E2E/an-external-portal-service' })).toBeVisible() + await expect( + page.getByRole("link", { name: "E2E/an-external-portal-service" }), + ).toBeVisible(); await page.goto( `/${context.team.slug}/${serviceProps.slug}/published?analytics=false`, @@ -279,7 +294,11 @@ test.describe("Flow creation, publish and preview", () => { }); await clickContinue({ page }); - await answerQuestion({ page, title: externalPortalFlowData.title, answer: externalPortalFlowData.answers[0] }); + await answerQuestion({ + page, + title: externalPortalFlowData.title, + answer: externalPortalFlowData.answers[0], + }); await clickContinue({ page }); await answerTextInput(page, { diff --git a/e2e/tests/ui-driven/src/helpers/addComponent.ts b/e2e/tests/ui-driven/src/helpers/addComponent.ts index 9e4e09fadc..3e6c9c28ef 100644 --- a/e2e/tests/ui-driven/src/helpers/addComponent.ts +++ b/e2e/tests/ui-driven/src/helpers/addComponent.ts @@ -123,9 +123,13 @@ const createBaseComponent = async ( case ComponentType.InternalPortal: await page.getByPlaceholder("Portal name").fill(title || ""); break; - case ComponentType.ExternalPortal: - await page.getByTestId('flowId').selectOption(`${contextDefaults.team.slug}/${externalPortalServiceProps.slug}`); - break; + case ComponentType.ExternalPortal: + await page + .getByTestId("flowId") + .selectOption( + `${contextDefaults.team.slug}/${externalPortalServiceProps.slug}`, + ); + break; default: throw new Error(`Unsupported type: ${type}`); } @@ -403,9 +407,5 @@ export const createExternalPortal = async ( page: Page, locatingNode: Locator, ) => { - await createBaseComponent( - page, - locatingNode, - ComponentType.ExternalPortal, - ); + await createBaseComponent(page, locatingNode, ComponentType.ExternalPortal); }; diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index 0f808d9c5f..1992537d5b 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -101,8 +101,7 @@ export const externalPortalServiceProps = { export const externalPortalFlowData = { title: "Is this an External Portal?", -answers:[ "It is an external portal", - "No it is not an External Portal",] + answers: ["It is an external portal", "No it is not an External Portal"], }; export async function tearDownTestContext(context: Context) { @@ -225,10 +224,7 @@ async function deleteSession(adminGQLClient: GraphQLClient, context) { } } -async function deletePublishedFlow( - adminGQLClient: GraphQLClient, - flow: Flow, -) { +async function deletePublishedFlow(adminGQLClient: GraphQLClient, flow: Flow) { if (flow?.publishedId) { log(`deleting published flow ${flow?.publishedId}`); await adminGQLClient.request( @@ -242,7 +238,7 @@ async function deletePublishedFlow( } } -async function deleteFlow(adminGQLClient: GraphQLClient, flow: Flow ) { +async function deleteFlow(adminGQLClient: GraphQLClient, flow: Flow) { if (flow?.id) { log(`deleting flow ${flow?.id}`); await adminGQLClient.request( @@ -264,9 +260,7 @@ async function deleteFlow(adminGQLClient: GraphQLClient, flow: Flow ) { { slug: flow?.slug }, ); if (response.flows.length && response.flows[0].id) { - log( - `deleting flow ${flow?.slug} flowId: ${response.flows[0].id}`, - ); + log(`deleting flow ${flow?.slug} flowId: ${response.flows[0].id}`); await adminGQLClient.request( `mutation DeleteTestFlow( $flowId: uuid!) { delete_flows_by_pk(id: $flowId) { diff --git a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts index 2523b61e6e..b510e8c1e2 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts @@ -23,11 +23,13 @@ import { mockPaymentRequest, modifiedInviteToPayFlow } from "./mocks"; let context: Context = { ...contextDefaults, - flows:[ { - slug: "invite-to-pay-test", - name: "Invite to pay test", - data: inviteToPayFlow, - }], + flows: [ + { + slug: "invite-to-pay-test", + name: "Invite to pay test", + data: inviteToPayFlow, + }, + ], sessionIds: [], // used to collect and clean up sessions }; @@ -119,10 +121,10 @@ test.describe("Agent journey @regression", async () => { const errorHeader = secondPage.getByRole("heading", { name: "Sorry, you can't make changes to this application", - }) + }); - await expect(errorHeader).toBeVisible() - await expect(secondPage.getByTestId("continue-button")).toBeHidden() + await expect(errorHeader).toBeVisible(); + await expect(secondPage.getByTestId("continue-button")).toBeHidden(); }); test("reconciliation does not apply to sessions with open payment requests", async ({ diff --git a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts index 9afbda7ce9..981d6b89aa 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts @@ -17,11 +17,13 @@ import { mockPaymentRequestDetails, mockSessionData } from "./mocks"; let context: Context = { ...contextDefaults, - flows: [{ - slug: "invite-to-pay-test", - name: "Invite to pay test", - data: inviteToPayFlow, - }], + flows: [ + { + slug: "invite-to-pay-test", + name: "Invite to pay test", + data: inviteToPayFlow, + }, + ], sessionIds: [], // used to collect and clean up sessions }; @@ -82,8 +84,8 @@ test.describe("Nominee journey @regression", async () => { }); test("navigating to a URL with an invalid ID", async ({ page }) => { - const invalidPaymentRequestURL = `/${context.team!.slug!}/${context.flows![0] - .slug!}/pay?analytics=false&paymentRequestId=INVALID-ID`; + const invalidPaymentRequestURL = `/${context.team!.slug!}/${context + .flows![0].slug!}/pay?analytics=false&paymentRequestId=INVALID-ID`; await page.goto(invalidPaymentRequestURL); await page.waitForLoadState("networkidle"); @@ -91,8 +93,8 @@ test.describe("Nominee journey @regression", async () => { }); test("navigating to a URL without a paymentRequestId", async ({ page }) => { - const invalidPaymentRequestURL = `/${context.team!.slug!}/${context.flows![0] - .slug!}/pay?analytics=false`; + const invalidPaymentRequestURL = `/${context.team!.slug!}/${context + .flows![0].slug!}/pay?analytics=false`; await page.goto(invalidPaymentRequestURL); await page.waitForLoadState("networkidle"); diff --git a/e2e/tests/ui-driven/src/pages/Editor.ts b/e2e/tests/ui-driven/src/pages/Editor.ts index a600f24667..8dd146229f 100644 --- a/e2e/tests/ui-driven/src/pages/Editor.ts +++ b/e2e/tests/ui-driven/src/pages/Editor.ts @@ -242,12 +242,9 @@ export class PlaywrightEditor { .fill("A notice inside a portal!"); await this.page.locator('button[form="modal"][type="submit"]').click(); } - + async createExternalPortal() { - await createExternalPortal( - this.page, - this.getNextNode(), - ); + await createExternalPortal(this.page, this.getNextNode()); } async createFeedback() { diff --git a/e2e/tests/ui-driven/src/pay.spec.ts b/e2e/tests/ui-driven/src/pay.spec.ts index 1275bf29a9..e878ba9ae3 100644 --- a/e2e/tests/ui-driven/src/pay.spec.ts +++ b/e2e/tests/ui-driven/src/pay.spec.ts @@ -20,11 +20,13 @@ import payFlow from "./mocks/flows/pay-flow.json"; let context: Context = { ...contextDefaults, - flows:[ { - slug: "pay-test", - name: "Pay test", - data: payFlow, - }], + flows: [ + { + slug: "pay-test", + name: "Pay test", + data: payFlow, + }, + ], sessionIds: [], // used to collect and clean up sessions }; const previewURL = `/${context.team!.slug!}/${context.flows![0] diff --git a/e2e/tests/ui-driven/src/save-and-return.spec.ts b/e2e/tests/ui-driven/src/save-and-return.spec.ts index be532a2a6c..5703e0c598 100644 --- a/e2e/tests/ui-driven/src/save-and-return.spec.ts +++ b/e2e/tests/ui-driven/src/save-and-return.spec.ts @@ -22,11 +22,13 @@ import { test.describe("Save and return", () => { let context: Context = { ...contextDefaults, - flows: [{ - slug: "e2e-save-and-return-test-flow", - name: "E2E Save and Return test flow", - data: simpleSendFlow, - }], + flows: [ + { + slug: "e2e-save-and-return-test-flow", + name: "E2E Save and Return test flow", + data: simpleSendFlow, + }, + ], }; const previewURL = `/${context.team?.slug}/${context.flows![0].slug}/published?analytics=false`; diff --git a/e2e/tests/ui-driven/src/sections.spec.ts b/e2e/tests/ui-driven/src/sections.spec.ts index d9648e5b12..2961a8f9c1 100644 --- a/e2e/tests/ui-driven/src/sections.spec.ts +++ b/e2e/tests/ui-driven/src/sections.spec.ts @@ -36,11 +36,13 @@ export enum SectionStatus { test.describe("Section statuses", () => { let context: Context = { ...contextDefaults, - flows:[ { - slug: "sections-test-flow", - name: "Sections test flow", - data: flow, - }], + flows: [ + { + slug: "sections-test-flow", + name: "Sections test flow", + data: flow, + }, + ], }; test.beforeAll(async () => { From c6eb03ccbd0f730bfe7ac88d2ed1df266f0e0c4a Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Wed, 27 Nov 2024 09:30:50 +0000 Subject: [PATCH 08/17] refine code and add navigation + publish helpers --- e2e/tests/ui-driven/src/create-flow.spec.ts | 45 ++++--------------- .../ui-driven/src/helpers/addComponent.ts | 2 +- e2e/tests/ui-driven/src/helpers/context.ts | 16 +++---- .../src/helpers/navigateAndPublish.ts | 31 +++++++++++++ 4 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index b8700f90eb..9407992289 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -25,6 +25,7 @@ import { createExternalPortal, createQuestionWithOptions, } from "./helpers/addComponent"; +import { navigateToService, publishService, turnServiceOnline } from "./helpers/navigateAndPublish"; test.describe("Flow creation, publish and preview", () => { let context: Context = { @@ -128,8 +129,7 @@ test.describe("Flow creation, publish and preview", () => { await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); - page.getByRole("button", { name: "PUBLISH", exact: true }).click(); + await publishService(page) const previewLink = page.getByRole("link", { name: "Open published service", @@ -164,15 +164,7 @@ test.describe("Flow creation, publish and preview", () => { await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - // Open flow settings - page.locator('[aria-label="Service settings"]').click(); - - // Toggle flow online - page.getByLabel("Offline").click(); - page.getByRole("button", { name: "Save", disabled: false }).click(); - await expect( - page.getByText("Service settings updated successfully"), - ).toBeVisible(); +await turnServiceOnline(page) // Exit back to main Editor page page.locator('[aria-label="Editor"]').click(); @@ -202,7 +194,7 @@ test.describe("Flow creation, publish and preview", () => { ); await editor.addNewService(); - // update context to allow flow to be torn down + // update context to allow new flow to be torn down context.flows?.push({ ...externalPortalServiceProps }); await createQuestionWithOptions( @@ -217,27 +209,10 @@ test.describe("Flow creation, publish and preview", () => { externalPortalFlowData.answers[1], ]); - page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); - await expect( - page.getByRole("heading", { name: "Check for changes to publish" }), - ).toBeVisible(); - page.getByRole("button", { name: "PUBLISH", exact: true }).click(); - - // Open flow settings - page.locator('[aria-label="Service settings"]').click(); - - // Toggle flow online - page.getByLabel("Offline").click(); - page.getByRole("button", { name: "Save", disabled: false }).click(); - await expect( - page.getByText("Service settings updated successfully"), - ).toBeVisible(); - - await page.goto(`/${context.team.slug}/${serviceProps.slug}`); + await publishService(page) + await turnServiceOnline(page) - await expect( - page.getByRole("link", { name: serviceProps.slug }), - ).toBeVisible(); + navigateToService(page, serviceProps.slug) await createExternalPortal(page, page.locator("li:nth-child(6)")); @@ -245,11 +220,7 @@ test.describe("Flow creation, publish and preview", () => { page.getByRole("link", { name: "E2E/an-external-portal-service" }), ).toBeVisible(); - page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); - await expect( - page.getByRole("heading", { name: "Check for changes to publish" }), - ).toBeVisible(); - page.getByRole("button", { name: "PUBLISH", exact: true }).click(); + await publishService(page) }); test("Can preview a published flow", async ({ diff --git a/e2e/tests/ui-driven/src/helpers/addComponent.ts b/e2e/tests/ui-driven/src/helpers/addComponent.ts index 3e6c9c28ef..760f110707 100644 --- a/e2e/tests/ui-driven/src/helpers/addComponent.ts +++ b/e2e/tests/ui-driven/src/helpers/addComponent.ts @@ -27,7 +27,7 @@ const createBaseComponent = async ( await page.getByPlaceholder("Notice").fill(title || ""); break; case ComponentType.Checklist: - await page.getByPlaceholder("Text").fill(title || "text"); + await page.getByPlaceholder("Text").fill(title || ""); if (options) { await createComponentOptions(options, "add new option", page); } diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index 1992537d5b..e581e47a1e 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -68,23 +68,23 @@ export async function setUpTestContext( } if ( context.flows && - context.flows![0].slug && - context.flows![0].data && - context.flows![0].name && + context.flows[0].slug && + context.flows[0].data && + context.flows[0].name && context.team?.id && context.user?.id ) { context.flows![0].id = await $admin.flow.create({ - slug: context.flows![0].slug, - name: context.flows![0].name, + slug: context.flows[0].slug, + name: context.flows[0].name, teamId: context.team.id, - data: context.flows![0]!.data!, + data: context.flows[0].data, status: "online", }); context.flows![0].publishedId = await $admin.flow.publish({ flow: { - id: context.flows![0].id, - data: context.flows![0]!.data!, + id: context.flows[0].id, + data: context.flows[0].data, }, publisherId: context.user!.id!, }); diff --git a/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts b/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts new file mode 100644 index 0000000000..d66b4055b2 --- /dev/null +++ b/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts @@ -0,0 +1,31 @@ +import { expect, Page } from "@playwright/test"; +import { contextDefaults } from "./context"; + +export const navigateToService = async (page: Page, slug:string) => { + + await page.goto(`/${contextDefaults.team.slug}/${slug}`); + + await expect( + page.getByRole("link", { name: slug }), + ).toBeVisible(); + + return true +} + +export const publishService = async (page:Page) => { + page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); + await expect( + page.getByRole("heading", { name: "Check for changes to publish" }), + ).toBeVisible(); + page.getByRole("button", { name: "PUBLISH", exact: true }).click(); +} + +export const turnServiceOnline = async (page: Page) =>{ + page.locator('[aria-label="Service settings"]').click(); + page.getByLabel("Offline").click(); + + page.getByRole("button", { name: "Save", disabled: false }).click(); + await expect( + page.getByText("Service settings updated successfully"), + ).toBeVisible(); +} \ No newline at end of file From 54e8cf1f41ca81437eb1e08370847bc009144c0f Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Wed, 27 Nov 2024 09:34:41 +0000 Subject: [PATCH 09/17] revert playwright config change --- e2e/tests/ui-driven/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/ui-driven/playwright.config.ts b/e2e/tests/ui-driven/playwright.config.ts index 2fe1fae19a..349006dbe1 100644 --- a/e2e/tests/ui-driven/playwright.config.ts +++ b/e2e/tests/ui-driven/playwright.config.ts @@ -14,7 +14,7 @@ dotenv.config({ path: "../../../.env" }); const config: PlaywrightTestConfig = { testDir: "src", /* Maximum time one test can run for. */ - timeout: 100 * 1000, + timeout: 45 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. From 754e635b5cbea309b72382f5284727de05bdb15a Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Wed, 27 Nov 2024 09:40:28 +0000 Subject: [PATCH 10/17] add type safety to flows --- e2e/tests/ui-driven/src/helpers/context.ts | 4 ++-- e2e/tests/ui-driven/src/helpers/globalHelpers.ts | 4 ++-- e2e/tests/ui-driven/src/helpers/userActions.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index e581e47a1e..d2c9aceea5 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -106,7 +106,7 @@ export const externalPortalFlowData = { export async function tearDownTestContext(context: Context) { const adminGQLClient = getGraphQLClient(); - if (context.flows && context.flows[0]) { + if (context.flows?.[0]) { await deleteSession(adminGQLClient, context); for (const flow of context.flows) { @@ -170,7 +170,7 @@ export async function findSessionId( id } }`, - { slug: context.flows![0].slug }, + { slug: context.flows?.[0].slug }, ); if (!flowResponse.flows.length || !flowResponse.flows[0].id) { return; diff --git a/e2e/tests/ui-driven/src/helpers/globalHelpers.ts b/e2e/tests/ui-driven/src/helpers/globalHelpers.ts index a40dc8081c..01a8b0933b 100644 --- a/e2e/tests/ui-driven/src/helpers/globalHelpers.ts +++ b/e2e/tests/ui-driven/src/helpers/globalHelpers.ts @@ -114,7 +114,7 @@ export async function modifyFlow({ modifiedFlow: FlowGraph; }) { const adminGQLClient = getGraphQLClient(); - if (!context.flows![0].id || !context.user?.id) { + if (!context.flows?.[0]?.slug || !context.user?.id) { throw new Error("context must have a flow and user"); } await adminGQLClient.request( @@ -132,7 +132,7 @@ export async function modifyFlow({ } `, { - flowId: context.flows![0].id, + flowId: context.flows?.[0].id, userId: context.user!.id, data: modifiedFlow, }, diff --git a/e2e/tests/ui-driven/src/helpers/userActions.ts b/e2e/tests/ui-driven/src/helpers/userActions.ts index f0fc59b317..703153fb01 100644 --- a/e2e/tests/ui-driven/src/helpers/userActions.ts +++ b/e2e/tests/ui-driven/src/helpers/userActions.ts @@ -35,7 +35,7 @@ export async function returnToSession({ sessionId: string; shouldContinue?: boolean; }) { - const returnURL = `/${context.team?.slug}/${context.flows![0].slug}/published?analytics=false&sessionId=${sessionId}`; + const returnURL = `/${context.team?.slug}/${context.flows?.[0].slug}/published?analytics=false&sessionId=${sessionId}`; log(`returning to http://localhost:3000/${returnURL}`); await page.goto(returnURL, { waitUntil: "load" }); await page.locator("#email").fill(context.user?.email); From e979d5d4681d3b8663f5b649c3770eb7596ccf35 Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Wed, 27 Nov 2024 09:45:00 +0000 Subject: [PATCH 11/17] refine flows type use --- e2e/tests/ui-driven/src/helpers/context.ts | 4 ++-- e2e/tests/ui-driven/src/invite-to-pay/helpers.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index d2c9aceea5..4356b9c07f 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -74,14 +74,14 @@ export async function setUpTestContext( context.team?.id && context.user?.id ) { - context.flows![0].id = await $admin.flow.create({ + context.flows[0].id = await $admin.flow.create({ slug: context.flows[0].slug, name: context.flows[0].name, teamId: context.team.id, data: context.flows[0].data, status: "online", }); - context.flows![0].publishedId = await $admin.flow.publish({ + context.flows[0].publishedId = await $admin.flow.publish({ flow: { id: context.flows[0].id, data: context.flows[0].data, diff --git a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts index ebaae1bc92..1680c5afd0 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts @@ -14,8 +14,8 @@ import { * Navigates to pay component whilst completing the minimum requirements for an Invite to Pay flow */ export async function navigateToPayComponent(page: Page, context: Context) { - const previewURL = `/${context.team!.slug!}/${context.flows![0] - .slug!}/published?analytics=false`; + const previewURL = `/${context.team!.slug!}/${context.flows?.[0] + .slug}/published?analytics=false`; await page.goto(previewURL); await fillInEmail({ page, context }); From 6152cd67d0ae8985a09dd310e1b71ff8b7ed69a5 Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Wed, 27 Nov 2024 10:24:54 +0000 Subject: [PATCH 12/17] fix data-testid --- .../src/pages/FlowEditor/components/forms/FormModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx index af7400b806..721c7a5b6e 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/forms/FormModal.tsx @@ -43,7 +43,7 @@ const NodeTypeSelect: React.FC<{ return ( { props.onChange(ev.target.value); }} From 377a77aadc3bf70b9b0f84892c1399b8e878b888 Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Wed, 27 Nov 2024 12:30:29 +0000 Subject: [PATCH 13/17] lint fix --- e2e/tests/ui-driven/src/create-flow.spec.ts | 18 +++++--- .../src/helpers/navigateAndPublish.ts | 43 +++++++++---------- .../ui-driven/src/invite-to-pay/helpers.ts | 5 ++- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index 9407992289..dab9f9d4db 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -25,7 +25,11 @@ import { createExternalPortal, createQuestionWithOptions, } from "./helpers/addComponent"; -import { navigateToService, publishService, turnServiceOnline } from "./helpers/navigateAndPublish"; +import { + navigateToService, + publishService, + turnServiceOnline, +} from "./helpers/navigateAndPublish"; test.describe("Flow creation, publish and preview", () => { let context: Context = { @@ -129,7 +133,7 @@ test.describe("Flow creation, publish and preview", () => { await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - await publishService(page) + await publishService(page); const previewLink = page.getByRole("link", { name: "Open published service", @@ -164,7 +168,7 @@ test.describe("Flow creation, publish and preview", () => { await page.goto(`/${context.team.slug}/${serviceProps.slug}`); -await turnServiceOnline(page) + await turnServiceOnline(page); // Exit back to main Editor page page.locator('[aria-label="Editor"]').click(); @@ -209,10 +213,10 @@ await turnServiceOnline(page) externalPortalFlowData.answers[1], ]); - await publishService(page) - await turnServiceOnline(page) + await publishService(page); + await turnServiceOnline(page); - navigateToService(page, serviceProps.slug) + navigateToService(page, serviceProps.slug); await createExternalPortal(page, page.locator("li:nth-child(6)")); @@ -220,7 +224,7 @@ await turnServiceOnline(page) page.getByRole("link", { name: "E2E/an-external-portal-service" }), ).toBeVisible(); - await publishService(page) + await publishService(page); }); test("Can preview a published flow", async ({ diff --git a/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts b/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts index d66b4055b2..1858537eb0 100644 --- a/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts +++ b/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts @@ -1,31 +1,28 @@ import { expect, Page } from "@playwright/test"; import { contextDefaults } from "./context"; -export const navigateToService = async (page: Page, slug:string) => { - - await page.goto(`/${contextDefaults.team.slug}/${slug}`); +export const navigateToService = async (page: Page, slug: string) => { + await page.goto(`/${contextDefaults.team.slug}/${slug}`); - await expect( - page.getByRole("link", { name: slug }), - ).toBeVisible(); + await expect(page.getByRole("link", { name: slug })).toBeVisible(); - return true -} + return true; +}; -export const publishService = async (page:Page) => { - page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); - await expect( - page.getByRole("heading", { name: "Check for changes to publish" }), - ).toBeVisible(); - page.getByRole("button", { name: "PUBLISH", exact: true }).click(); -} +export const publishService = async (page: Page) => { + page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); + await expect( + page.getByRole("heading", { name: "Check for changes to publish" }), + ).toBeVisible(); + page.getByRole("button", { name: "PUBLISH", exact: true }).click(); +}; -export const turnServiceOnline = async (page: Page) =>{ - page.locator('[aria-label="Service settings"]').click(); - page.getByLabel("Offline").click(); +export const turnServiceOnline = async (page: Page) => { + page.locator('[aria-label="Service settings"]').click(); + page.getByLabel("Offline").click(); - page.getByRole("button", { name: "Save", disabled: false }).click(); - await expect( - page.getByText("Service settings updated successfully"), - ).toBeVisible(); -} \ No newline at end of file + page.getByRole("button", { name: "Save", disabled: false }).click(); + await expect( + page.getByText("Service settings updated successfully"), + ).toBeVisible(); +}; diff --git a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts index 1680c5afd0..ff7b23239b 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts @@ -14,8 +14,9 @@ import { * Navigates to pay component whilst completing the minimum requirements for an Invite to Pay flow */ export async function navigateToPayComponent(page: Page, context: Context) { - const previewURL = `/${context.team!.slug!}/${context.flows?.[0] - .slug}/published?analytics=false`; + const previewURL = `/${context.team!.slug!}/${ + context.flows?.[0].slug + }/published?analytics=false`; await page.goto(previewURL); await fillInEmail({ page, context }); From eee94eef47fe9380c26e5d5311c68d39b1b67fe0 Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Fri, 29 Nov 2024 14:17:38 +0000 Subject: [PATCH 14/17] revert flows change and add expect to makePaymentRequest separate out types and service data lint fix --- .../src/create-flow-with-geospatial.spec.ts | 27 ++---- e2e/tests/ui-driven/src/create-flow.spec.ts | 43 ++++----- .../ui-driven/src/helpers/addComponent.ts | 3 +- e2e/tests/ui-driven/src/helpers/context.ts | 90 ++++++------------- .../ui-driven/src/helpers/globalHelpers.ts | 12 +-- .../src/helpers/navigateAndPublish.ts | 2 - .../ui-driven/src/helpers/serviceData.ts | 14 +++ e2e/tests/ui-driven/src/helpers/types.ts | 20 +++++ .../ui-driven/src/helpers/userActions.ts | 10 +-- .../ui-driven/src/invite-to-pay/agent.spec.ts | 20 ++--- .../ui-driven/src/invite-to-pay/helpers.ts | 12 +-- .../src/invite-to-pay/nominee.spec.ts | 33 ++++--- e2e/tests/ui-driven/src/login.spec.ts | 4 +- e2e/tests/ui-driven/src/pages/Editor.ts | 7 ++ e2e/tests/ui-driven/src/pay.spec.ts | 20 ++--- e2e/tests/ui-driven/src/refresh-page.spec.ts | 4 +- .../ui-driven/src/save-and-return.spec.ts | 18 ++-- e2e/tests/ui-driven/src/sections.spec.ts | 24 +++-- 18 files changed, 172 insertions(+), 191 deletions(-) create mode 100644 e2e/tests/ui-driven/src/helpers/serviceData.ts create mode 100644 e2e/tests/ui-driven/src/helpers/types.ts diff --git a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts index 5a41195bfa..bf88ebeb8b 100644 --- a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from "@playwright/test"; -import type { Context } from "./helpers/context"; import { contextDefaults, setUpTestContext, @@ -9,15 +8,17 @@ import { getTeamPage } from "./helpers/getPage"; import { createAuthenticatedSession } from "./helpers/globalHelpers"; import { answerFindProperty, clickContinue } from "./helpers/userActions"; import { PlaywrightEditor } from "./pages/Editor"; +import { + publishService, + turnServiceOnline, +} from "./helpers/navigateAndPublish"; +import { TestContext } from "./helpers/types"; +import { serviceProps } from "./helpers/serviceData"; test.describe("Flow creation, publish and preview", () => { - let context: Context = { + let context: TestContext = { ...contextDefaults, }; - const serviceProps = { - name: "A Test Service", - slug: "a-test-service", - }; test.beforeAll(async () => { try { @@ -44,9 +45,6 @@ test.describe("Flow creation, publish and preview", () => { page.on("dialog", (dialog) => dialog.accept(serviceProps.name)); await editor.addNewService(); - // update context to allow flow to be torn down - context.flows = [{ ...serviceProps }]; - await editor.createFindProperty(); await expect(editor.nodeList).toContainText(["Find property"]); await editor.createInternalPortal(); @@ -79,8 +77,7 @@ test.describe("Flow creation, publish and preview", () => { }); // publish flow await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - page.getByRole("button", { name: "CHECK FOR CHANGES TO PUBLISH" }).click(); - page.getByRole("button", { name: "PUBLISH", exact: true }).click(); + publishService(page); let previewLink = page.getByRole("link", { name: "Open published service", @@ -89,13 +86,7 @@ test.describe("Flow creation, publish and preview", () => { await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - // Toggle flow online - page.locator('[aria-label="Service settings"]').click(); - page.getByLabel("Offline").click(); - page.getByRole("button", { name: "Save", disabled: false }).click(); - await expect( - page.getByText("Service settings updated successfully"), - ).toBeVisible(); + turnServiceOnline(page); // Exit back to main Editor page page.locator('[aria-label="Editor"]').click(); diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index dab9f9d4db..f233fd9d1d 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -1,9 +1,6 @@ import { Browser, expect, test } from "@playwright/test"; -import type { Context } from "./helpers/context"; import { contextDefaults, - externalPortalFlowData, - externalPortalServiceProps, setUpTestContext, tearDownTestContext, } from "./helpers/context"; @@ -21,18 +18,20 @@ import { clickContinue, } from "./helpers/userActions"; import { PlaywrightEditor } from "./pages/Editor"; -import { - createExternalPortal, - createQuestionWithOptions, -} from "./helpers/addComponent"; +import { createExternalPortal } from "./helpers/addComponent"; import { navigateToService, publishService, turnServiceOnline, } from "./helpers/navigateAndPublish"; +import { TestContext } from "./helpers/types"; +import { + externalPortalFlowData, + externalPortalServiceProps, +} from "./helpers/serviceData"; test.describe("Flow creation, publish and preview", () => { - let context: Context = { + let context: TestContext = { ...contextDefaults, }; const serviceProps = { @@ -67,7 +66,7 @@ test.describe("Flow creation, publish and preview", () => { await editor.addNewService(); // update context to allow flow to be torn down - context.flows = [{ ...serviceProps }]; + context.flow = { ...serviceProps }; await editor.createQuestion(); await editor.createNoticeOnEachBranch(); @@ -131,8 +130,7 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); - await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - + await navigateToService(page, serviceProps.slug); await publishService(page); const previewLink = page.getByRole("link", { @@ -166,8 +164,7 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); - await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - + await navigateToService(page, serviceProps.slug); await turnServiceOnline(page); // Exit back to main Editor page @@ -199,24 +196,22 @@ test.describe("Flow creation, publish and preview", () => { await editor.addNewService(); // update context to allow new flow to be torn down - context.flows?.push({ ...externalPortalServiceProps }); + context.externalPortalFlow = { ...externalPortalServiceProps }; + + const { title, answers } = externalPortalFlowData; + + await editor.createQuestionWithOptions(title, answers); - await createQuestionWithOptions( - page, - editor.firstNode, - externalPortalFlowData.title, - externalPortalFlowData.answers, - ); await expect(editor.nodeList).toContainText([ - externalPortalFlowData.title, - externalPortalFlowData.answers[0], - externalPortalFlowData.answers[1], + title, + answers[0], + answers[1], ]); await publishService(page); await turnServiceOnline(page); - navigateToService(page, serviceProps.slug); + await navigateToService(page, serviceProps.slug); await createExternalPortal(page, page.locator("li:nth-child(6)")); diff --git a/e2e/tests/ui-driven/src/helpers/addComponent.ts b/e2e/tests/ui-driven/src/helpers/addComponent.ts index 760f110707..61c0f8ca4e 100644 --- a/e2e/tests/ui-driven/src/helpers/addComponent.ts +++ b/e2e/tests/ui-driven/src/helpers/addComponent.ts @@ -1,6 +1,7 @@ import { ComponentType } from "@opensystemslab/planx-core/types"; import { expect, Locator, Page } from "@playwright/test"; -import { contextDefaults, externalPortalServiceProps } from "./context"; +import { contextDefaults } from "./context"; +import { externalPortalServiceProps } from "./serviceData"; const createBaseComponent = async ( page: Page, diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index 4356b9c07f..70069ed844 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -3,32 +3,12 @@ import { GraphQLClient, gql } from "graphql-request"; import { sign } from "jsonwebtoken"; import assert from "node:assert"; import { log } from "./globalHelpers"; +import { $admin } from "../../../api-driven/src/client"; +import { Flow, TestContext } from "./types"; -type NewTeam = Parameters[0]; - -export interface Flow { - id?: string; - publishedId?: number; - slug: string; - name: string; - data?: object; -} - -export interface Context { - user: { - id?: number; - firstName: string; - lastName: string; - email: string; - isPlatformAdmin: boolean; - }; - team: { id?: number } & NewTeam; - flows?: Flow[]; - sessionIds?: string[]; -} - -export const contextDefaults: Context = { +export const contextDefaults: TestContext = { user: { + id: 0, firstName: "Test", lastName: "Test", email: "simulate-delivered@notifications.service.gov.uk", @@ -49,10 +29,10 @@ export const contextDefaults: Context = { }; export async function setUpTestContext( - initialContext: Context, -): Promise { + initialContext: TestContext, +): Promise { const $admin = getCoreDomainClient(); - const context: Context = { ...initialContext }; + const context: TestContext = { ...initialContext }; if (context.user) { context.user.id = await $admin.user.create(context.user); } @@ -67,24 +47,24 @@ export async function setUpTestContext( }); } if ( - context.flows && - context.flows[0].slug && - context.flows[0].data && - context.flows[0].name && + context.flow && + context.flow.slug && + context.flow.data && + context.flow.name && context.team?.id && context.user?.id ) { - context.flows[0].id = await $admin.flow.create({ - slug: context.flows[0].slug, - name: context.flows[0].name, + context.flow.id = await $admin.flow.create({ + slug: context.flow.slug, + name: context.flow.name, teamId: context.team.id, - data: context.flows[0].data, + data: context.flow.data, status: "online", }); - context.flows[0].publishedId = await $admin.flow.publish({ + context.flow.publishedId = await $admin.flow.publish({ flow: { - id: context.flows[0].id, - data: context.flows[0].data, + id: context.flow.id, + data: context.flow.data, }, publisherId: context.user!.id!, }); @@ -94,27 +74,10 @@ export async function setUpTestContext( return context; } -export const externalPortalServiceProps = { - name: "An External Portal Service", - slug: "an-external-portal-service", -}; - -export const externalPortalFlowData = { - title: "Is this an External Portal?", - answers: ["It is an external portal", "No it is not an External Portal"], -}; - -export async function tearDownTestContext(context: Context) { +export async function tearDownTestContext(context: TestContext) { const adminGQLClient = getGraphQLClient(); - if (context.flows?.[0]) { - await deleteSession(adminGQLClient, context); - - for (const flow of context.flows) { - await deleteFlow(adminGQLClient, flow); - if (flow.publishedId) { - await deletePublishedFlow(adminGQLClient, flow); - } - } + if (context.flow || context.externalPortalFlow) { + await $admin.flow._destroyAll(); } if (context.user) { await deleteUser(adminGQLClient, context); @@ -170,7 +133,7 @@ export async function findSessionId( id } }`, - { slug: context.flows?.[0].slug }, + { slug: context.flow.slug }, ); if (!flowResponse.flows.length || !flowResponse.flows[0].id) { return; @@ -273,7 +236,7 @@ async function deleteFlow(adminGQLClient: GraphQLClient, flow: Flow) { } } -async function deleteUser(adminGQLClient: GraphQLClient, context: Context) { +async function deleteUser(adminGQLClient: GraphQLClient, context: TestContext) { if (context.user?.id) { log(`deleting user ${context.user?.id}`); await adminGQLClient.request( @@ -310,7 +273,7 @@ async function deleteUser(adminGQLClient: GraphQLClient, context: Context) { } } -async function deleteTeam(adminGQLClient: GraphQLClient, context: Context) { +async function deleteTeam(adminGQLClient: GraphQLClient, context: TestContext) { if (context.team?.id) { log(`deleting team ${context.team?.id}`); await adminGQLClient.request( @@ -347,7 +310,10 @@ async function deleteTeam(adminGQLClient: GraphQLClient, context: Context) { } } -async function setupGovPaySecret($admin: CoreDomainClient, context: Context) { +async function setupGovPaySecret( + $admin: CoreDomainClient, + context: TestContext, +) { try { await $admin.client.request( gql` diff --git a/e2e/tests/ui-driven/src/helpers/globalHelpers.ts b/e2e/tests/ui-driven/src/helpers/globalHelpers.ts index 01a8b0933b..f580449673 100644 --- a/e2e/tests/ui-driven/src/helpers/globalHelpers.ts +++ b/e2e/tests/ui-driven/src/helpers/globalHelpers.ts @@ -1,8 +1,8 @@ import { FlowGraph } from "@opensystemslab/planx-core/types"; import type { Browser, Page, Request } from "@playwright/test"; import { gql } from "graphql-request"; -import type { Context } from "./context"; import { generateAuthenticationToken, getGraphQLClient } from "./context"; +import { TestContext } from "./types"; // Test card numbers to be used in gov.uk sandbox environment // reference: https://docs.payments.service.gov.uk/testing_govuk_pay/#if-you-39-re-using-a-test-39-sandbox-39-account @@ -87,7 +87,7 @@ export async function getSessionId(page: Page): Promise { return sessionId; } -export async function addSessionToContext(page: Page, context: Context) { +export async function addSessionToContext(page: Page, context: TestContext) { const sessionId = await getSessionId(page); context.sessionIds!.push(sessionId); return sessionId; @@ -95,7 +95,7 @@ export async function addSessionToContext(page: Page, context: Context) { export async function waitForPaymentResponse( page: Page, - context: Context, + context: TestContext, ): Promise<{ paymentId: string; state?: { status: string } }> { const { payment_id: paymentId, state } = await page .waitForResponse((response) => { @@ -110,11 +110,11 @@ export async function modifyFlow({ context, modifiedFlow, }: { - context: Context; + context: TestContext; modifiedFlow: FlowGraph; }) { const adminGQLClient = getGraphQLClient(); - if (!context.flows?.[0]?.slug || !context.user?.id) { + if (!context.flow?.slug || !context.user?.id) { throw new Error("context must have a flow and user"); } await adminGQLClient.request( @@ -132,7 +132,7 @@ export async function modifyFlow({ } `, { - flowId: context.flows?.[0].id, + flowId: context.flow?.id, userId: context.user!.id, data: modifiedFlow, }, diff --git a/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts b/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts index 1858537eb0..d5d66138fa 100644 --- a/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts +++ b/e2e/tests/ui-driven/src/helpers/navigateAndPublish.ts @@ -5,8 +5,6 @@ export const navigateToService = async (page: Page, slug: string) => { await page.goto(`/${contextDefaults.team.slug}/${slug}`); await expect(page.getByRole("link", { name: slug })).toBeVisible(); - - return true; }; export const publishService = async (page: Page) => { diff --git a/e2e/tests/ui-driven/src/helpers/serviceData.ts b/e2e/tests/ui-driven/src/helpers/serviceData.ts new file mode 100644 index 0000000000..b1fda6a053 --- /dev/null +++ b/e2e/tests/ui-driven/src/helpers/serviceData.ts @@ -0,0 +1,14 @@ +export const serviceProps = { + name: "A Test Service", + slug: "a-test-service", +}; + +export const externalPortalServiceProps = { + name: "An External Portal Service", + slug: "an-external-portal-service", +}; + +export const externalPortalFlowData = { + title: "Is this an External Portal?", + answers: ["It is an external portal", "No it is not an External Portal"], +}; diff --git a/e2e/tests/ui-driven/src/helpers/types.ts b/e2e/tests/ui-driven/src/helpers/types.ts new file mode 100644 index 0000000000..194f55510d --- /dev/null +++ b/e2e/tests/ui-driven/src/helpers/types.ts @@ -0,0 +1,20 @@ +import { CoreDomainClient } from "@opensystemslab/planx-core"; +import { User } from "@opensystemslab/planx-core/dist/types"; + +type NewTeam = Parameters[0]; + +export interface Flow { + id?: string; + publishedId?: number; + slug: string; + name: string; + data?: object; +} + +export interface TestContext { + user: Omit; + team: { id?: number } & NewTeam; + flow?: Flow; + externalPortalFlow?: Flow; + sessionIds?: string[]; +} diff --git a/e2e/tests/ui-driven/src/helpers/userActions.ts b/e2e/tests/ui-driven/src/helpers/userActions.ts index 703153fb01..35d24d5ae9 100644 --- a/e2e/tests/ui-driven/src/helpers/userActions.ts +++ b/e2e/tests/ui-driven/src/helpers/userActions.ts @@ -1,16 +1,16 @@ import type { Locator, Page } from "@playwright/test"; import { expect } from "@playwright/test"; import { setupOSMockResponse } from "../mocks/osPlacesResponse"; -import type { Context } from "./context"; import { findSessionId, getGraphQLClient } from "./context"; import { TEST_EMAIL, log, waitForDebugLog } from "./globalHelpers"; +import { TestContext } from "./types"; export async function saveSession({ page, context, }: { page: Page; - context: Context; + context: TestContext; }): Promise { const pageResponsePromise = page.waitForResponse((response) => { return response.url().includes("/send-email/save"); @@ -31,11 +31,11 @@ export async function returnToSession({ shouldContinue = true, }: { page: Page; - context: Context; + context: TestContext; sessionId: string; shouldContinue?: boolean; }) { - const returnURL = `/${context.team?.slug}/${context.flows?.[0].slug}/published?analytics=false&sessionId=${sessionId}`; + const returnURL = `/${context.team?.slug}/${context.flow?.slug}/published?analytics=false&sessionId=${sessionId}`; log(`returning to http://localhost:3000/${returnURL}`); await page.goto(returnURL, { waitUntil: "load" }); await page.locator("#email").fill(context.user?.email); @@ -81,7 +81,7 @@ export async function fillInEmail({ context, }: { page: Page; - context: Context; + context: TestContext; }) { await page.locator("#email").fill(context.user.email); await page.locator("#confirmEmail").fill(context.user.email); diff --git a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts index b510e8c1e2..13a915fddc 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts @@ -1,6 +1,5 @@ import { BrowserContext, Page, expect, test } from "@playwright/test"; import { - Context, contextDefaults, getGraphQLClient, setUpTestContext, @@ -20,16 +19,15 @@ import { navigateToPayComponent, } from "./helpers"; import { mockPaymentRequest, modifiedInviteToPayFlow } from "./mocks"; +import { TestContext } from "../helpers/types"; -let context: Context = { +let context: TestContext = { ...contextDefaults, - flows: [ - { - slug: "invite-to-pay-test", - name: "Invite to pay test", - data: inviteToPayFlow, - }, - ], + flow: { + slug: "invite-to-pay-test", + name: "Invite to pay test", + data: inviteToPayFlow, + }, sessionIds: [], // used to collect and clean up sessions }; @@ -108,7 +106,7 @@ test.describe("Agent journey @regression", async () => { const sessionId = await makePaymentRequest({ page: firstPage, context }); // Resume session - const resumeLink = `/${context.team!.slug!}/${context.flows![0].slug}/published?analytics=false&sessionId=${sessionId}`; + const resumeLink = `/${context.team!.slug!}/${context.flow?.slug}/published?analytics=false&sessionId=${sessionId}`; const secondPage = await browserContext.newPage(); await secondPage.goto(resumeLink); await expect( @@ -137,7 +135,7 @@ test.describe("Agent journey @regression", async () => { await modifyFlow({ context, modifiedFlow: modifiedInviteToPayFlow }); // Navigate to resume session link - const resumeLink = `/${context.team!.slug!}/${context.flows![0].slug}/published?analytics=false&sessionId=${sessionId}`; + const resumeLink = `/${context.team!.slug!}/${context.flow?.slug}/published?analytics=false&sessionId=${sessionId}`; const secondPage = await browserContext.newPage(); await secondPage.goto(resumeLink); await expect( diff --git a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts index ff7b23239b..b7a21b667d 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts @@ -1,7 +1,6 @@ import { PaymentRequest } from "@opensystemslab/planx-core/dist/types"; -import type { Page } from "@playwright/test"; +import { expect, type Page } from "@playwright/test"; import { GraphQLClient, gql } from "graphql-request"; -import { Context } from "../helpers/context"; import { TEST_EMAIL, addSessionToContext, log } from "../helpers/globalHelpers"; import { answerChecklist, @@ -9,13 +8,14 @@ import { answerFindProperty, fillInEmail, } from "../helpers/userActions"; +import { TestContext } from "../helpers/types"; /** * Navigates to pay component whilst completing the minimum requirements for an Invite to Pay flow */ -export async function navigateToPayComponent(page: Page, context: Context) { +export async function navigateToPayComponent(page: Page, context: TestContext) { const previewURL = `/${context.team!.slug!}/${ - context.flows?.[0].slug + context.flow?.slug }/published?analytics=false`; await page.goto(previewURL); @@ -85,7 +85,7 @@ export async function makePaymentRequest({ context, }: { page: Page; - context: Context; + context: TestContext; }) { await navigateToPayComponent(page, context); const sessionId = await addSessionToContext(page, context); @@ -96,6 +96,6 @@ export async function makePaymentRequest({ await answerInviteToPayForm(page); page.getByRole("button", { name: "Send invitation to pay" }).click(); await page.waitForResponse((res) => res.url().includes("invite-to-pay")); - + await expect(page.getByText("Error generating payment")).toBeHidden(); return sessionId; } diff --git a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts index 981d6b89aa..f4b8b941b2 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts @@ -3,7 +3,6 @@ import { APIRequestContext, Page, expect, test } from "@playwright/test"; import { GraphQLClient, gql } from "graphql-request"; import { v4 as uuidV4 } from "uuid"; import { - Context, contextDefaults, getGraphQLClient, setUpTestContext, @@ -14,16 +13,15 @@ import { fillGovUkCardDetails } from "../helpers/userActions"; import inviteToPayFlow from "../mocks/flows/invite-to-pay-flow"; import { getPaymentRequestBySessionId } from "./helpers"; import { mockPaymentRequestDetails, mockSessionData } from "./mocks"; +import { TestContext } from "../helpers/types"; -let context: Context = { +let context: TestContext = { ...contextDefaults, - flows: [ - { - slug: "invite-to-pay-test", - name: "Invite to pay test", - data: inviteToPayFlow, - }, - ], + flow: { + slug: "invite-to-pay-test", + name: "Invite to pay test", + data: inviteToPayFlow, + }, sessionIds: [], // used to collect and clean up sessions }; @@ -84,8 +82,9 @@ test.describe("Nominee journey @regression", async () => { }); test("navigating to a URL with an invalid ID", async ({ page }) => { - const invalidPaymentRequestURL = `/${context.team!.slug!}/${context - .flows![0].slug!}/pay?analytics=false&paymentRequestId=INVALID-ID`; + const invalidPaymentRequestURL = `/${context.team!.slug!}/${ + context.flow?.slug + }/pay?analytics=false&paymentRequestId=INVALID-ID`; await page.goto(invalidPaymentRequestURL); await page.waitForLoadState("networkidle"); @@ -93,8 +92,7 @@ test.describe("Nominee journey @regression", async () => { }); test("navigating to a URL without a paymentRequestId", async ({ page }) => { - const invalidPaymentRequestURL = `/${context.team!.slug!}/${context - .flows![0].slug!}/pay?analytics=false`; + const invalidPaymentRequestURL = `/${context.team!.slug!}/${context.flow?.slug}/pay?analytics=false`; await page.goto(invalidPaymentRequestURL); await page.waitForLoadState("networkidle"); @@ -128,8 +126,7 @@ async function navigateToPaymentRequestPage( paymentRequest: PaymentRequest, page: Page, ) { - const paymentRequestURL = `/${context.team!.slug!}/${context.flows![0] - .slug!}/pay?analytics=false&paymentRequestId=${paymentRequest.id}`; + const paymentRequestURL = `/${context.team!.slug!}/${context.flow?.slug}/pay?analytics=false&paymentRequestId=${paymentRequest.id}`; await page.goto(paymentRequestURL); await page.waitForLoadState("networkidle"); } @@ -151,7 +148,7 @@ async function createSession({ client, sessionId, }: { - context: Context; + context: TestContext; client: GraphQLClient; sessionId: string; }) { @@ -173,11 +170,11 @@ async function createSession({ await client.request[]>>(mutation, { id: sessionId, data: { - id: context.flows![0].id, + id: context.flow?.id, ...mockSessionData, }, email: context.user.email, - flowId: context.flows![0].id, + flowId: context.flow?.id, }); } diff --git a/e2e/tests/ui-driven/src/login.spec.ts b/e2e/tests/ui-driven/src/login.spec.ts index 23bfbecf83..0f0d73de8d 100644 --- a/e2e/tests/ui-driven/src/login.spec.ts +++ b/e2e/tests/ui-driven/src/login.spec.ts @@ -1,14 +1,14 @@ import { expect, test } from "@playwright/test"; -import type { Context } from "./helpers/context"; import { contextDefaults, setUpTestContext, tearDownTestContext, } from "./helpers/context"; import { createAuthenticatedSession } from "./helpers/globalHelpers"; +import { TestContext } from "./helpers/types"; test.describe("Login", () => { - let context: Context = { + let context: TestContext = { ...contextDefaults, }; diff --git a/e2e/tests/ui-driven/src/pages/Editor.ts b/e2e/tests/ui-driven/src/pages/Editor.ts index 8dd146229f..8e0da26ba5 100644 --- a/e2e/tests/ui-driven/src/pages/Editor.ts +++ b/e2e/tests/ui-driven/src/pages/Editor.ts @@ -74,6 +74,13 @@ export class PlaywrightEditor { ).toBeVisible(); } + async createQuestionWithOptions(title: string, answers: string[]) { + await createQuestionWithOptions(this.page, this.firstNode, title, answers); + await expect( + this.page.locator("a").filter({ hasText: this.answers.questionText }), + ).toBeVisible(); + } + async createNoticeOnEachBranch() { // Add a notice to the "Yes" path await createNotice( diff --git a/e2e/tests/ui-driven/src/pay.spec.ts b/e2e/tests/ui-driven/src/pay.spec.ts index e878ba9ae3..b0a1614870 100644 --- a/e2e/tests/ui-driven/src/pay.spec.ts +++ b/e2e/tests/ui-driven/src/pay.spec.ts @@ -2,7 +2,6 @@ import type { SessionData } from "@opensystemslab/planx-core/types"; import type { Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { GraphQLClient, gql } from "graphql-request"; -import type { Context } from "./helpers/context"; import { contextDefaults, getGraphQLClient, @@ -17,20 +16,19 @@ import { } from "./helpers/globalHelpers"; import { fillGovUkCardDetails, submitCardDetails } from "./helpers/userActions"; import payFlow from "./mocks/flows/pay-flow.json"; +import { TestContext } from "./helpers/types"; -let context: Context = { +let context: TestContext = { ...contextDefaults, - flows: [ - { - slug: "pay-test", - name: "Pay test", - data: payFlow, - }, - ], + flow: { + slug: "pay-test", + name: "Pay test", + data: payFlow, + }, + sessionIds: [], // used to collect and clean up sessions }; -const previewURL = `/${context.team!.slug!}/${context.flows![0] - .slug!}/published?analytics=false`; +const previewURL = `/${context.team!.slug!}/${context.flow?.slug}/published?analytics=false`; const payButtonText = "Pay now using GOV.UK Pay"; diff --git a/e2e/tests/ui-driven/src/refresh-page.spec.ts b/e2e/tests/ui-driven/src/refresh-page.spec.ts index 4edd79c190..37481963be 100644 --- a/e2e/tests/ui-driven/src/refresh-page.spec.ts +++ b/e2e/tests/ui-driven/src/refresh-page.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from "@playwright/test"; -import type { Context } from "./helpers/context"; import { contextDefaults, setUpTestContext, @@ -9,9 +8,10 @@ import { createAuthenticatedSession, isGetUserRequest, } from "./helpers/globalHelpers"; +import { TestContext } from "./helpers/types"; test.describe("Refresh page", () => { - let context: Context = { + let context: TestContext = { ...contextDefaults, }; diff --git a/e2e/tests/ui-driven/src/save-and-return.spec.ts b/e2e/tests/ui-driven/src/save-and-return.spec.ts index 5703e0c598..9631fc177a 100644 --- a/e2e/tests/ui-driven/src/save-and-return.spec.ts +++ b/e2e/tests/ui-driven/src/save-and-return.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from "@playwright/test"; -import type { Context } from "./helpers/context"; import { contextDefaults, setUpTestContext, @@ -18,19 +17,18 @@ import { modifiedSimpleSendFlow, simpleSendFlow, } from "./mocks/flows/save-and-return-flows"; +import { TestContext } from "./helpers/types"; test.describe("Save and return", () => { - let context: Context = { + let context: TestContext = { ...contextDefaults, - flows: [ - { - slug: "e2e-save-and-return-test-flow", - name: "E2E Save and Return test flow", - data: simpleSendFlow, - }, - ], + flow: { + slug: "e2e-save-and-return-test-flow", + name: "E2E Save and Return test flow", + data: simpleSendFlow, + }, }; - const previewURL = `/${context.team?.slug}/${context.flows![0].slug}/published?analytics=false`; + const previewURL = `/${context.team?.slug}/${context.flow?.slug}/published?analytics=false`; test.beforeAll(async () => { try { diff --git a/e2e/tests/ui-driven/src/sections.spec.ts b/e2e/tests/ui-driven/src/sections.spec.ts index 2961a8f9c1..159cc99c34 100644 --- a/e2e/tests/ui-driven/src/sections.spec.ts +++ b/e2e/tests/ui-driven/src/sections.spec.ts @@ -1,7 +1,6 @@ import type { FlowGraph } from "@opensystemslab/planx-core/types"; import { expect, test } from "@playwright/test"; import { gql } from "graphql-request"; -import type { Context } from "./helpers/context"; import { contextDefaults, getGraphQLClient, @@ -21,6 +20,7 @@ import { saveSession, } from "./helpers/userActions"; import { flow, updatedQuestionAnswers } from "./mocks/flows/sections-flow"; +import { TestContext } from "./helpers/types"; // TODO: move this type to planx-core // also defined in editor.planx.uk/src/types.ts @@ -34,15 +34,13 @@ export enum SectionStatus { } test.describe("Section statuses", () => { - let context: Context = { + let context: TestContext = { ...contextDefaults, - flows: [ - { - slug: "sections-test-flow", - name: "Sections test flow", - data: flow, - }, - ], + flow: { + slug: "sections-test-flow", + name: "Sections test flow", + data: flow, + }, }; test.beforeAll(async () => { @@ -55,7 +53,7 @@ test.describe("Section statuses", () => { }); test.beforeEach(async ({ page }) => { - const previewURL = `/${context.team?.slug}/${context.flows![0].slug}/published?analytics=false`; + const previewURL = `/${context.team?.slug}/${context.flow?.slug}/published?analytics=false`; await page.goto(previewURL); }); @@ -533,11 +531,11 @@ async function modifyFlow({ context, flowData, }: { - context: Context; + context: TestContext; flowData: FlowGraph; }) { const adminGQLClient = getGraphQLClient(); - if (!context.flows![0].id || !context.user?.id) { + if (!context.flow?.id || !context.user?.id) { throw new Error("context must have a flow and user"); } await adminGQLClient.request( @@ -555,7 +553,7 @@ async function modifyFlow({ } `, { - flowId: context.flows![0].id, + flowId: context.flow?.id, userId: context.user!.id, data: flowData, }, From 3826fade32490f47e6a648053385b4773308c9c5 Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Fri, 29 Nov 2024 16:57:23 +0000 Subject: [PATCH 15/17] fix bugs with user and navigation --- .../src/create-flow-with-geospatial.spec.ts | 14 ++++++------ e2e/tests/ui-driven/src/create-flow.spec.ts | 4 ++-- e2e/tests/ui-driven/src/helpers/context.ts | 22 +++++++------------ e2e/tests/ui-driven/src/helpers/types.ts | 2 +- .../ui-driven/src/invite-to-pay/agent.spec.ts | 4 ++-- .../src/invite-to-pay/nominee.spec.ts | 4 ++-- e2e/tests/ui-driven/src/login.spec.ts | 4 ++-- e2e/tests/ui-driven/src/pages/Editor.ts | 2 +- e2e/tests/ui-driven/src/pay.spec.ts | 4 ++-- e2e/tests/ui-driven/src/refresh-page.spec.ts | 4 ++-- .../ui-driven/src/save-and-return.spec.ts | 4 ++-- e2e/tests/ui-driven/src/sections.spec.ts | 4 ++-- 12 files changed, 33 insertions(+), 39 deletions(-) diff --git a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts index bf88ebeb8b..2c56fffbfe 100644 --- a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts @@ -9,6 +9,7 @@ import { createAuthenticatedSession } from "./helpers/globalHelpers"; import { answerFindProperty, clickContinue } from "./helpers/userActions"; import { PlaywrightEditor } from "./pages/Editor"; import { + navigateToService, publishService, turnServiceOnline, } from "./helpers/navigateAndPublish"; @@ -24,13 +25,13 @@ test.describe("Flow creation, publish and preview", () => { try { context = await setUpTestContext(context); } catch (error) { - await tearDownTestContext(context); + await tearDownTestContext(); throw error; } }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test("Create a flow", async ({ browser }) => { @@ -76,17 +77,16 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); // publish flow - await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - publishService(page); + await navigateToService(page, serviceProps.slug) + await publishService(page); let previewLink = page.getByRole("link", { name: "Open published service", }); await expect(previewLink).toBeVisible(); - await page.goto(`/${context.team.slug}/${serviceProps.slug}`); - - turnServiceOnline(page); + await navigateToService(page, serviceProps.slug) + await turnServiceOnline(page); // Exit back to main Editor page page.locator('[aria-label="Editor"]').click(); diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index f233fd9d1d..2f83e90e85 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -44,13 +44,13 @@ test.describe("Flow creation, publish and preview", () => { context = await setUpTestContext(context); } catch (error) { // ensure proper teardown if setup fails - await tearDownTestContext(context); + await tearDownTestContext(); throw error; } }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test("Create a flow", async ({ browser }) => { diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index 70069ed844..b1174eeb6b 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -3,12 +3,11 @@ import { GraphQLClient, gql } from "graphql-request"; import { sign } from "jsonwebtoken"; import assert from "node:assert"; import { log } from "./globalHelpers"; -import { $admin } from "../../../api-driven/src/client"; import { Flow, TestContext } from "./types"; export const contextDefaults: TestContext = { user: { - id: 0, + id:0, firstName: "Test", lastName: "Test", email: "simulate-delivered@notifications.service.gov.uk", @@ -28,13 +27,15 @@ export const contextDefaults: TestContext = { }, }; +const $admin = getCoreDomainClient(); + export async function setUpTestContext( initialContext: TestContext, ): Promise { - const $admin = getCoreDomainClient(); const context: TestContext = { ...initialContext }; if (context.user) { - context.user.id = await $admin.user.create(context.user); + const {firstName, lastName, email, isPlatformAdmin} = context.user + context.user.id = await $admin.user.create({firstName, lastName, email, isPlatformAdmin}); } if (context.team) { context.team.id = await $admin.team.create({ @@ -74,17 +75,10 @@ export async function setUpTestContext( return context; } -export async function tearDownTestContext(context: TestContext) { - const adminGQLClient = getGraphQLClient(); - if (context.flow || context.externalPortalFlow) { +export async function tearDownTestContext() { await $admin.flow._destroyAll(); - } - if (context.user) { - await deleteUser(adminGQLClient, context); - } - if (context.team) { - await deleteTeam(adminGQLClient, context); - } + await $admin.user._destroyAll() + await $admin.team._destroyAll() } export function generateAuthenticationToken(userId: string) { diff --git a/e2e/tests/ui-driven/src/helpers/types.ts b/e2e/tests/ui-driven/src/helpers/types.ts index 194f55510d..40404ac5cb 100644 --- a/e2e/tests/ui-driven/src/helpers/types.ts +++ b/e2e/tests/ui-driven/src/helpers/types.ts @@ -12,7 +12,7 @@ export interface Flow { } export interface TestContext { - user: Omit; + user: Pick team: { id?: number } & NewTeam; flow?: Flow; externalPortalFlow?: Flow; diff --git a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts index 13a915fddc..b0c889b288 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/agent.spec.ts @@ -39,12 +39,12 @@ test.describe("Agent journey @regression", async () => { context = await setUpTestContext(context); } catch (e) { // ensure proper teardown if setup fails - await tearDownTestContext(context); + await tearDownTestContext(); throw e; } }); - test.afterAll(async () => await tearDownTestContext(context)); + test.afterAll(async () => await tearDownTestContext()); test("agent can send a payment request", async ({ page }) => { await navigateToPayComponent(page, context); diff --git a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts index f4b8b941b2..97631a2ff1 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/nominee.spec.ts @@ -35,13 +35,13 @@ test.describe("Nominee journey @regression", async () => { context = await setUpTestContext(context); } catch (e) { // ensure proper teardown if setup fails - await tearDownTestContext(context); + await tearDownTestContext(); throw e; } }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test("responding to a valid payment request", async ({ page, request }) => { diff --git a/e2e/tests/ui-driven/src/login.spec.ts b/e2e/tests/ui-driven/src/login.spec.ts index 0f0d73de8d..083c1897de 100644 --- a/e2e/tests/ui-driven/src/login.spec.ts +++ b/e2e/tests/ui-driven/src/login.spec.ts @@ -17,13 +17,13 @@ test.describe("Login", () => { context = await setUpTestContext(context); } catch (error) { // ensure proper teardown if setup fails - await tearDownTestContext(context); + await tearDownTestContext(); throw error; } }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test("setting a cookie bypasses login", async ({ browser }) => { diff --git a/e2e/tests/ui-driven/src/pages/Editor.ts b/e2e/tests/ui-driven/src/pages/Editor.ts index 8e0da26ba5..d19ae67146 100644 --- a/e2e/tests/ui-driven/src/pages/Editor.ts +++ b/e2e/tests/ui-driven/src/pages/Editor.ts @@ -77,7 +77,7 @@ export class PlaywrightEditor { async createQuestionWithOptions(title: string, answers: string[]) { await createQuestionWithOptions(this.page, this.firstNode, title, answers); await expect( - this.page.locator("a").filter({ hasText: this.answers.questionText }), + this.page.locator("a").filter({ hasText: title }), ).toBeVisible(); } diff --git a/e2e/tests/ui-driven/src/pay.spec.ts b/e2e/tests/ui-driven/src/pay.spec.ts index b0a1614870..e33509d03d 100644 --- a/e2e/tests/ui-driven/src/pay.spec.ts +++ b/e2e/tests/ui-driven/src/pay.spec.ts @@ -40,13 +40,13 @@ test.describe("Gov Pay integration @regression", async () => { context = await setUpTestContext(context); } catch (e) { // ensure proper teardown if setup fails - await tearDownTestContext(context); + await tearDownTestContext(); throw e; } }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test("a successful payment", async ({ page }) => { diff --git a/e2e/tests/ui-driven/src/refresh-page.spec.ts b/e2e/tests/ui-driven/src/refresh-page.spec.ts index 37481963be..744696fe77 100644 --- a/e2e/tests/ui-driven/src/refresh-page.spec.ts +++ b/e2e/tests/ui-driven/src/refresh-page.spec.ts @@ -20,13 +20,13 @@ test.describe("Refresh page", () => { context = await setUpTestContext(context); } catch (error) { // ensure proper teardown if setup fails - await tearDownTestContext(context); + await tearDownTestContext(); throw error; } }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test("user data persists on page refresh @regression", async ({ diff --git a/e2e/tests/ui-driven/src/save-and-return.spec.ts b/e2e/tests/ui-driven/src/save-and-return.spec.ts index 9631fc177a..d84cdda237 100644 --- a/e2e/tests/ui-driven/src/save-and-return.spec.ts +++ b/e2e/tests/ui-driven/src/save-and-return.spec.ts @@ -34,13 +34,13 @@ test.describe("Save and return", () => { try { context = await setUpTestContext(context); } catch (e) { - await tearDownTestContext(context); + await tearDownTestContext(); throw e; } }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test.describe("email", () => { diff --git a/e2e/tests/ui-driven/src/sections.spec.ts b/e2e/tests/ui-driven/src/sections.spec.ts index 159cc99c34..b321506214 100644 --- a/e2e/tests/ui-driven/src/sections.spec.ts +++ b/e2e/tests/ui-driven/src/sections.spec.ts @@ -47,7 +47,7 @@ test.describe("Section statuses", () => { try { context = await setUpTestContext(context); } catch (e) { - await tearDownTestContext(context); + await tearDownTestContext(); throw e; } }); @@ -58,7 +58,7 @@ test.describe("Section statuses", () => { }); test.afterAll(async () => { - await tearDownTestContext(context); + await tearDownTestContext(); }); test.describe("a straight-through journey", () => { From 362b5b6bc722be2dfe29c4ae1b7b89639f2aeff9 Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Fri, 29 Nov 2024 17:02:36 +0000 Subject: [PATCH 16/17] lint:fix --- .../src/create-flow-with-geospatial.spec.ts | 4 ++-- e2e/tests/ui-driven/src/helpers/context.ts | 17 +++++++++++------ e2e/tests/ui-driven/src/helpers/types.ts | 5 ++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts index 2c56fffbfe..38cb922659 100644 --- a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts @@ -77,7 +77,7 @@ test.describe("Flow creation, publish and preview", () => { userId: context.user!.id!, }); // publish flow - await navigateToService(page, serviceProps.slug) + await navigateToService(page, serviceProps.slug); await publishService(page); let previewLink = page.getByRole("link", { @@ -85,7 +85,7 @@ test.describe("Flow creation, publish and preview", () => { }); await expect(previewLink).toBeVisible(); - await navigateToService(page, serviceProps.slug) + await navigateToService(page, serviceProps.slug); await turnServiceOnline(page); // Exit back to main Editor page diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index b1174eeb6b..6db53a36dd 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -7,7 +7,7 @@ import { Flow, TestContext } from "./types"; export const contextDefaults: TestContext = { user: { - id:0, + id: 0, firstName: "Test", lastName: "Test", email: "simulate-delivered@notifications.service.gov.uk", @@ -34,8 +34,13 @@ export async function setUpTestContext( ): Promise { const context: TestContext = { ...initialContext }; if (context.user) { - const {firstName, lastName, email, isPlatformAdmin} = context.user - context.user.id = await $admin.user.create({firstName, lastName, email, isPlatformAdmin}); + const { firstName, lastName, email, isPlatformAdmin } = context.user; + context.user.id = await $admin.user.create({ + firstName, + lastName, + email, + isPlatformAdmin, + }); } if (context.team) { context.team.id = await $admin.team.create({ @@ -76,9 +81,9 @@ export async function setUpTestContext( } export async function tearDownTestContext() { - await $admin.flow._destroyAll(); - await $admin.user._destroyAll() - await $admin.team._destroyAll() + await $admin.flow._destroyAll(); + await $admin.user._destroyAll(); + await $admin.team._destroyAll(); } export function generateAuthenticationToken(userId: string) { diff --git a/e2e/tests/ui-driven/src/helpers/types.ts b/e2e/tests/ui-driven/src/helpers/types.ts index 40404ac5cb..25c3bff286 100644 --- a/e2e/tests/ui-driven/src/helpers/types.ts +++ b/e2e/tests/ui-driven/src/helpers/types.ts @@ -12,7 +12,10 @@ export interface Flow { } export interface TestContext { - user: Pick + user: Pick< + User, + "firstName" | "lastName" | "email" | "isPlatformAdmin" | "id" + >; team: { id?: number } & NewTeam; flow?: Flow; externalPortalFlow?: Flow; From deefde71aaa8c683325494c3adabcc2fb6eda8fd Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Mon, 2 Dec 2024 12:44:19 +0000 Subject: [PATCH 17/17] add new comments and changes --- e2e/tests/ui-driven/src/create-flow.spec.ts | 4 + e2e/tests/ui-driven/src/helpers/context.ts | 153 +------------------- 2 files changed, 5 insertions(+), 152 deletions(-) diff --git a/e2e/tests/ui-driven/src/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow.spec.ts index 2f83e90e85..f681ad01de 100644 --- a/e2e/tests/ui-driven/src/create-flow.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow.spec.ts @@ -208,17 +208,21 @@ test.describe("Flow creation, publish and preview", () => { answers[1], ]); + // We are publishing the Ext Portal service and turning it online await publishService(page); await turnServiceOnline(page); + // We switch back to the original service await navigateToService(page, serviceProps.slug); + // Add our ext portal to the middle of the service await createExternalPortal(page, page.locator("li:nth-child(6)")); await expect( page.getByRole("link", { name: "E2E/an-external-portal-service" }), ).toBeVisible(); + // publish the changes we've made to the original service await publishService(page); }); diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index 6db53a36dd..792a99a1ed 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -2,8 +2,7 @@ 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"; -import { Flow, TestContext } from "./types"; +import { TestContext } from "./types"; export const contextDefaults: TestContext = { user: { @@ -159,156 +158,6 @@ export async function findSessionId( } } -async function deleteSession(adminGQLClient: GraphQLClient, context) { - if (context.sessionIds) { - for (const sessionId of context.sessionIds) { - await adminGQLClient.request( - `mutation DeleteTestSession( $sessionId: uuid!) { - delete_lowcal_sessions_by_pk(id: $sessionId) { - id - } - }`, - { sessionId }, - ); - } - } - const sessionId = await findSessionId(adminGQLClient, context); - if (sessionId) { - log(`deleting session id: ${sessionId}`); - await adminGQLClient.request( - `mutation DeleteTestSession( $sessionId: uuid!) { - delete_lowcal_sessions_by_pk(id: $sessionId) { - id - } - }`, - { sessionId }, - ); - } -} - -async function deletePublishedFlow(adminGQLClient: GraphQLClient, flow: Flow) { - if (flow?.publishedId) { - log(`deleting published flow ${flow?.publishedId}`); - await adminGQLClient.request( - `mutation DeleteTestPublishedFlow( $publishedFlowId: Int!) { - delete_published_flows_by_pk(id: $publishedFlowId) { - id - } - }`, - { publishedFlowId: flow?.publishedId }, - ); - } -} - -async function deleteFlow(adminGQLClient: GraphQLClient, flow: Flow) { - if (flow?.id) { - log(`deleting flow ${flow?.id}`); - await adminGQLClient.request( - `mutation DeleteTestFlow($flowId: uuid!) { - delete_flows_by_pk(id: $flowId) { - id - } - }`, - { flowId: flow?.id }, - ); - } else if (flow?.slug) { - // try deleting via slug (when cleaning up from a previously failed test) - const response: { flows: { id: string }[] } = await adminGQLClient.request( - `query GetFlowBySlug($slug: String!) { - flows(where: {slug: {_eq: $slug}}) { - id - } - }`, - { slug: flow?.slug }, - ); - if (response.flows.length && response.flows[0].id) { - log(`deleting flow ${flow?.slug} flowId: ${response.flows[0].id}`); - await adminGQLClient.request( - `mutation DeleteTestFlow( $flowId: uuid!) { - delete_flows_by_pk(id: $flowId) { - id - } - }`, - { flowId: response.flows[0].id }, - ); - } - } -} - -async function deleteUser(adminGQLClient: GraphQLClient, context: TestContext) { - if (context.user?.id) { - log(`deleting user ${context.user?.id}`); - await adminGQLClient.request( - `mutation DeleteTestUser($userId: Int!) { - delete_users_by_pk(id: $userId) { - id - } - }`, - { userId: context.user?.id }, - ); - } else if (context.user?.email) { - // try deleting via email (when cleaning up from a previously failed test) - const response: { users: { id: number }[] } = await adminGQLClient.request( - `query GetUserByEmail($email: String!) { - users(where: {email: {_eq: $email}}) { - id - } - }`, - { email: context.user?.email }, - ); - if (response.users.length && response.users[0].id) { - log( - `deleting user ${context.user?.email} userId: ${response.users[0].id}`, - ); - await adminGQLClient.request( - `mutation DeleteTestUser($userId: Int!) { - delete_users_by_pk(id: $userId) { - id - } - }`, - { userId: response.users[0].id }, - ); - } - } -} - -async function deleteTeam(adminGQLClient: GraphQLClient, context: TestContext) { - if (context.team?.id) { - log(`deleting team ${context.team?.id}`); - await adminGQLClient.request( - `mutation DeleteTestTeam( $teamId: Int!) { - delete_teams_by_pk(id: $teamId) { - id - } - }`, - { teamId: context.team?.id }, - ); - } else if (context.team?.slug) { - // try deleting via slug (when cleaning up from a previously failed test) - const response: { teams: { id: number }[] } = await adminGQLClient.request( - `query GetTeamBySlug( $slug: String!) { - teams(where: {slug: {_eq: $slug}}) { - id - } - }`, - { slug: context.team?.slug }, - ); - if (response.teams.length && response.teams[0].id) { - log( - `deleting team ${context.team?.slug} teamId: ${response.teams[0].id}`, - ); - await adminGQLClient.request( - `mutation DeleteTestTeam( $teamId: Int!) { - delete_teams_by_pk(id: $teamId) { - id - } - }`, - { teamId: response.teams[0].id }, - ); - } - } -} - async function setupGovPaySecret( $admin: CoreDomainClient, context: TestContext,