Skip to content

Commit

Permalink
Page Object Model for Playwright tests (#737)
Browse files Browse the repository at this point in the history
Co-authored-by: benmartin-coforma <[email protected]>
  • Loading branch information
jessabean and benmartin-coforma committed Sep 13, 2024
1 parent aadcbf5 commit 6ed7650
Show file tree
Hide file tree
Showing 17 changed files with 435 additions and 141 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ jobs:
needs:
- deploy
- register-runner
- e2e-test
- a11y-tests
if: ${{ always() && !cancelled() && needs.deploy.result == 'success' && github.ref_name != 'production' }}
timeout-minutes: 60
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default defineConfig({
testDir: "./tests/e2e",
testMatch: ["**/*.spec.js", "**/*.spec.ts"],
/* Run tests in files in parallel */
fullyParallel: true,
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
Expand Down Expand Up @@ -47,7 +47,7 @@ export default defineConfig({
webServer: {
command: process.env.CI ? "" : "./run local",
url: process.env.BASE_URL || "http://localhost:3000",
reuseExistingServer: process.env.CI || false,
reuseExistingServer: !!process.env.CI,
stdout: "pipe",
},
});
2 changes: 1 addition & 1 deletion services/ui-src/src/verbiage/pages/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default {
downloadText: "User Guide and Help File",
link: {
text: "Enter SAR online",
route: "sar/",
route: "sar",
},
accordion: {
buttonLabel: "When is the MFP SAR due?",
Expand Down
32 changes: 16 additions & 16 deletions tests/e2e/pages/home.spec.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { test, expect } from "@playwright/test";
import { test, expect } from "../utils/fixtures/base";
import { logInStateUser, logInAdminUser, e2eA11y } from "../helpers";

test("Should see the correct home page as a state user", async ({ page }) => {
test("Should see the correct home page as a state user", async ({
page,
stateHomePage,
}) => {
await page.goto("/");
await logInStateUser(page);

await expect(
page.getByRole("button", { name: "Enter Work Plan online" })
).toBeVisible();
await expect(
page.getByRole("button", { name: "Enter SAR online" })
).toBeVisible();
await stateHomePage.isReady();
await expect(stateHomePage.wpButton).toBeVisible();
await expect(stateHomePage.sarButton).toBeVisible();
});

test("Should see the correct home page as an admin user", async ({ page }) => {
test("Should see the correct home page as an admin user", async ({
page,
adminHomePage,
}) => {
await page.goto("/");
await logInAdminUser(page);

await expect(
page.getByRole("combobox", {
name: "List of states, including District of Columbia and Puerto Rico",
})
).toBeVisible();
await adminHomePage.isReady();
await expect(adminHomePage.dropdown).toBeVisible();
});

test("Is accessible on all device types for state user", async ({ page }) => {
Expand Down
53 changes: 22 additions & 31 deletions tests/e2e/sar/create.spec.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,47 @@
import { expect, test } from "@playwright/test";
import { test, expect } from "../utils/fixtures/base";
import { currentYear } from "../../seeds/helpers";
import {
archiveExistingWPs,
firstPeriod,
logInStateUser,
stateAbbreviation,
stateName,
} from "../helpers";

// TODO: Unskip
test.skip("State user can create a SAR", async ({ page }) => {
test.skip("State user can create a SAR", async ({
page,
stateHomePage,
sarDashboard,
}) => {
await archiveExistingWPs(page);
await logInStateUser(page);

// TODO: Seed WP

// View SARs
await page.getByRole("button", { name: "Enter SAR online" }).click();
await expect(page).toHaveURL("/sar/");
await page.waitForResponse(`**/reports/SAR/${stateAbbreviation}`);
await logInStateUser(page);
await stateHomePage.sarButton.click();

// Create SAR
await page
.getByRole("button", {
name: "Add new MFP SAR submission",
})
.click();

const modal = page.getByRole("dialog");
await expect(modal).toBeVisible();
await expect(modal).toContainText("Add new MFP SAR submission");
await expect(
page.getByRole("textbox", { name: "Associated MFP Work Plan" })
).toHaveValue(
await sarDashboard.isReady();
await sarDashboard.createButton.click();
await expect(sarDashboard.modal).toBeVisible();
await expect(sarDashboard.modal).toContainText("Add new MFP SAR submission");
await expect(sarDashboard.associatedWP).toHaveValue(
`${stateName} MFP Work Plan ${currentYear} - Period ${firstPeriod}`
);

await page.getByRole("radio", { name: "No" }).click();
await page.getByRole("button", { name: "Save" }).click();
await sarDashboard.createNewSAR();

// Confirm created SAR is in table
await expect(page.getByRole("table")).toBeVisible();

const row = page
.getByRole("row", {
name: `${stateName} MFP SAR ${currentYear} - Period ${firstPeriod}`,
})
.first();
await expect(sarDashboard.firstReport).toBeVisible();

const editIcon = row.getByRole("button", { name: "Edit Report" });
const editIcon = sarDashboard.firstReport.getByRole("button", {
name: "Edit Report",
});
await expect(editIcon).toBeVisible();

const editButton = row.getByRole("button", { name: "Edit", exact: true });
const editButton = sarDashboard.firstReport.getByRole("button", {
name: "Edit",
exact: true,
});
await expect(editButton).toBeVisible();
});
23 changes: 23 additions & 0 deletions tests/e2e/utils/fixtures/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { mergeTests, test as base } from "@playwright/test";
import { test as sarTest } from "./sar.ts";
import { test as wpTest } from "./wp.ts";
import StateHomePage from "../pageObjects/stateHome.page";
import AdminHomePage from "../pageObjects/adminHome.page";

type CustomFixtures = {
stateHomePage: StateHomePage;
adminHomePage: AdminHomePage;
};

export const baseTest = base.extend<CustomFixtures>({
stateHomePage: async ({ page }, use) => {
await use(new StateHomePage(page));
},
adminHomePage: async ({ page }, use) => {
await use(new AdminHomePage(page));
},
});

export const test = mergeTests(baseTest, sarTest, wpTest);

export { expect } from "@playwright/test";
12 changes: 12 additions & 0 deletions tests/e2e/utils/fixtures/sar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { test as base } from "@playwright/test";
import { SARDashboardPage } from "../pageObjects/sar/sarDashboard.page";

type SARFixtures = {
sarDashboard: SARDashboardPage;
};

export const test = base.extend<SARFixtures>({
sarDashboard: async ({ page }, use) => {
await use(new SARDashboardPage(page));
},
});
22 changes: 22 additions & 0 deletions tests/e2e/utils/fixtures/wp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { test as base } from "@playwright/test";
import { WPDashboardPage } from "../pageObjects/wp/wpDashboard.page";
import { WPGeneralInformationPage } from "../pageObjects/wp/wpGeneral.page";
import { WPReviewAndSubmitPage } from "../pageObjects/wp/wpReviewAndSubmit.page";

type WPFixtures = {
wpDashboard: WPDashboardPage;
wpGeneralInformation: WPGeneralInformationPage;
wpReviewAndSubmit: WPReviewAndSubmitPage;
};

export const test = base.extend<WPFixtures>({
wpDashboard: async ({ page }, use) => {
await use(new WPDashboardPage(page));
},
wpGeneralInformation: async ({ page }, use) => {
await use(new WPGeneralInformationPage(page));
},
wpReviewAndSubmit: async ({ page }, use) => {
await use(new WPReviewAndSubmitPage(page));
},
});
41 changes: 41 additions & 0 deletions tests/e2e/utils/pageObjects/adminHome.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Locator, Page } from "@playwright/test";
import BasePage from "./base.page";

export default class AdminHomePage extends BasePage {
public path = "/";

readonly page: Page;
readonly title: Locator;
readonly dropdown: Locator;

constructor(page: Page) {
super(page);
this.page = page;
this.title = page.getByRole("heading", {
name: "View State/Territory Reports",
});
this.dropdown = page.getByRole("combobox", {
name: "List of states, including District of Columbia and Puerto Rico",
});
}

public async selectWP() {
await this.page.getByRole("radio", { name: "MFP Work Plan" }).click();
}

public async selectSAR() {
await this.page
.getByRole("radio", {
name: "MFP Semi-Annual Progress Report (SAR)",
})
.click();
}

public async goToDashboard() {
await this.page
.getByRole("button", {
name: "Go to Report Dashboard",
})
.click();
}
}
28 changes: 28 additions & 0 deletions tests/e2e/utils/pageObjects/base.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect, Locator, Page } from "@playwright/test";

export default class BasePage {
public path = "/";

readonly page: Page;
readonly title: Locator;
readonly continueButton: Locator;
readonly previousButton: Locator;

constructor(page: Page) {
this.page = page;
this.title = page.getByRole("heading", {
name: "Money Follows the Person",
});
this.continueButton = page.getByRole("button", { name: "Continue" });
this.previousButton = page.getByRole("button", { name: "Previous" });
}

public async goto() {
await this.page.goto(this.path);
}

public async isReady() {
await this.title.isVisible();
return expect(this.page).toHaveURL(this.path);
}
}
56 changes: 56 additions & 0 deletions tests/e2e/utils/pageObjects/sar/sarDashboard.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Locator, Page } from "@playwright/test";
import BasePage from "../base.page";
import { firstPeriod, stateAbbreviation, stateName } from "../../../helpers";
import { currentYear } from "../../../../seeds/helpers";

export class SARDashboardPage extends BasePage {
public path = "/sar";

readonly page: Page;
readonly title: Locator;
readonly createButton: Locator;
readonly modal: Locator;
readonly associatedWP: Locator;
readonly firstReport: Locator;

constructor(page: Page) {
super(page);
this.page = page;
this.title = this.page.getByRole("heading", {
name: `${stateName} MFP Semi-Annual Progress Report (SAR)`,
});
this.createButton = this.page.getByRole("button", {
name: "Add new MFP SAR submission",
});
this.modal = this.page.getByRole("dialog");
this.associatedWP = this.modal.getByRole("textbox", {
name: "Associated MFP Work Plan",
});
this.firstReport = this.page
.getByRole("row", {
name: `${stateName} MFP SAR ${currentYear} - Period ${firstPeriod}`,
})
.first();
}

public async reportsReady() {
await this.getReports();
await this.page.getByRole("rowheader", { name: "Submission name" });
}

public async getReports() {
await this.page.waitForResponse(
(response) =>
response.url().includes(`/reports/SAR/${stateAbbreviation}`) &&
response.status() == 200
);
}

public async createNewSAR() {
const noRadio = this.modal.getByRole("radio", { name: "No" });
const saveButton = this.modal.getByRole("button", { name: "Save" });

await noRadio.click();
await saveButton.click();
}
}
23 changes: 23 additions & 0 deletions tests/e2e/utils/pageObjects/stateHome.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Locator, Page } from "@playwright/test";
import BasePage from "./base.page";

export default class StateHomePage extends BasePage {
public path = "/";

readonly page: Page;
readonly title: Locator;
readonly wpButton: Locator;
readonly sarButton: Locator;

constructor(page: Page) {
super(page);
this.page = page;
this.title = page.getByRole("heading", {
name: "Money Follows the Person (MFP) Portal",
});
this.wpButton = page.getByRole("button", {
name: "Enter Work Plan online",
});
this.sarButton = page.getByRole("button", { name: "Enter SAR online" });
}
}
Loading

0 comments on commit 6ed7650

Please sign in to comment.