Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

[Issue #37]: finish e2e tests #38

Merged
merged 6 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ To run E2E tests using VS Code:
1. Download the VS Code extension described in these [Playwright docs](https://playwright.dev/docs/running-tests#run-tests-in-vs-code)
2. Follow the [instructions](https://playwright.dev/docs/getting-started-vscode#running-tests) Playwright provides

In CI, the "Front-end Checks" workflow (`.github/workflows/ci-frontend.yml`) summary will include an "Artifacts" section where there is an attached "playwright-report". [Playwright docs](https://playwright.dev/docs/ci-intro#html-report) describe how to view HTML Report in more detail.
Playwright E2E tests run "local-to-local", requiring both the frontend and the API to be running for the tests to pass - and for the database to be seeded with data.
Copy link
Collaborator Author

@rylew1 rylew1 May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the only new change after approval

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of adding to the instructions:

+ - `cd ../api && make init db-seed-local start` (prerequisite to start the API)
  - `npx playwright install --with-deps`

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I saw the approval and merged very quickly - can open a small follow on


In CI, the "Front-end Checks" workflow (`.github/workflows/ci-frontend-e2e.yml`) summary will include an "Artifacts" section where there is an attached "playwright-report". [Playwright docs](https://playwright.dev/docs/ci-intro#html-report) describe how to view HTML Report in more detail.

## 🤖 Type checking, linting, and formatting

Expand Down
79 changes: 75 additions & 4 deletions frontend/tests/e2e/search/search.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import {
clickAccordionWithTitle,
clickLastPaginationPage,
clickMobileNavMenu,
clickPaginationPageNumber,
clickSearchNavLink,
expectCheckboxIDIsChecked,
expectSortBy,
expectURLContainsQueryParam,
fillSearchInputAndSubmit,
getFirstSearchResultTitle,
getLastSearchResultTitle,
getMobileMenuButton,
getNumberOfOpportunitySearchResults,
getSearchInput,
hasMobileMenu,
refreshPageWithCurrentURL,
selectOppositeSortOption,
selectSortBy,
toggleCheckboxes,
waitForSearchResultsLoaded,
} from "./searchUtil";
waitForSearchResultsInitialLoad,
} from "./searchSpecUtil";
import { expect, test } from "@playwright/test";

test("should navigate from index to search page", async ({ page }) => {
Expand Down Expand Up @@ -63,6 +69,7 @@ test.describe("Search page tests", () => {

expectURLContainsQueryParam(page, "query", searchTerm);

// eslint-disable-next-line testing-library/prefer-screen-queries
const resultsHeading = page.getByRole("heading", {
name: /0 Opportunities/i,
});
Expand Down Expand Up @@ -91,7 +98,7 @@ test.describe("Search page tests", () => {
await expect(loadingIndicator).toBeVisible();
await expect(loadingIndicator).toBeHidden();
});
test("should retain filters in a new tab", async ({ page }) => {
test("should refresh and retain filters in a new tab", async ({ page }) => {
// Set all inputs, then refresh the page. Those same inputs should be
// set from query params.
const searchTerm = "education";
Expand Down Expand Up @@ -119,7 +126,7 @@ test.describe("Search page tests", () => {

await selectSortBy(page, "agencyDesc");

await waitForSearchResultsLoaded(page);
await waitForSearchResultsInitialLoad(page);
await fillSearchInputAndSubmit(searchTerm, page);
await toggleCheckboxes(page, statusCheckboxes, "status");

Expand Down Expand Up @@ -167,4 +174,68 @@ test.describe("Search page tests", () => {
await expectCheckboxIDIsChecked(page, `#${checkboxID}`);
}
});

test("resets page back to 1 when choosing a filter", async ({ page }) => {
await clickPaginationPageNumber(page, 2);

// Verify that page 1 is highlighted
let currentPageButton = page.locator(".usa-pagination__button.usa-current");
await expect(currentPageButton).toHaveAttribute("aria-label", "Page 2");

// Select the 'Closed' checkbox under 'Opportunity status'
const statusCheckboxes = {
"status-closed": "closed",
};
await toggleCheckboxes(page, statusCheckboxes, "status");

// Wait for the page to reload
await waitForSearchResultsInitialLoad(page);

// Verify that page 1 is highlighted
currentPageButton = page.locator(".usa-pagination__button.usa-current");
await expect(currentPageButton).toHaveAttribute("aria-label", "Page 1");

// It should not have a page query param set
expectURLContainsQueryParam(page, "page", "1", false);
});

test("last result becomes first result when flipping sort order", async ({
page,
}) => {
await clickLastPaginationPage(page);

await waitForSearchResultsInitialLoad(page);

const lastSearchResultTitle = await getLastSearchResultTitle(page);

await selectOppositeSortOption(page);

const firstSearchResultTitle = await getFirstSearchResultTitle(page);

expect(firstSearchResultTitle).toBe(lastSearchResultTitle);
});

test("number of results is the same with none or all opportunity status checked", async ({
page,
}) => {
const initialNumberOfOpportunityResults =
await getNumberOfOpportunitySearchResults(page);

// check all 4 boxes
const statusCheckboxes = {
"status-forecasted": "forecasted",
"status-posted": "posted",
"status-closed": "closed",
"status-archived": "archived",
};

await toggleCheckboxes(page, statusCheckboxes, "status");

const updatedNumberOfOpportunityResults =
await getNumberOfOpportunitySearchResults(page);

expect(initialNumberOfOpportunityResults).toBe(
updatedNumberOfOpportunityResults,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ export function expectURLContainsQueryParam(
page: Page,
queryParamName: string,
queryParamValue: string,
shouldContain = true,
) {
const currentURL = page.url();
expect(currentURL).toContain(`${queryParamName}=${queryParamValue}`);
const queryParam = `${queryParamName}=${queryParamValue}`;

if (shouldContain) {
expect(currentURL).toContain(queryParam);
} else {
expect(currentURL).not.toContain(queryParam);
}
}

export async function waitForURLContainsQueryParam(
Expand Down Expand Up @@ -116,7 +123,7 @@ export async function expectSortBy(page: Page, value: string) {
expect(selectedValue).toBe(value);
}

export async function waitForSearchResultsLoaded(page: Page) {
export async function waitForSearchResultsInitialLoad(page: Page) {
// Wait for number of opportunities to show
const resultsHeading = page.locator('h2:has-text("Opportunities")');
await resultsHeading.waitFor({ state: "visible", timeout: 60000 });
Expand All @@ -130,3 +137,71 @@ export async function clickAccordionWithTitle(
.locator(`button.usa-accordion__button:has-text("${accordionTitle}")`)
.click();
}

export async function clickPaginationPageNumber(
page: Page,
pageNumber: number,
) {
const paginationButton = page.locator(
`button[data-testid="pagination-page-number"][aria-label="Page ${pageNumber}"]`,
);
await paginationButton.first().click();
}

export async function clickLastPaginationPage(page: Page) {
const paginationButtons = page.locator("li > button");
const count = await paginationButtons.count();

// must be more than 1 page
if (count > 2) {
await paginationButtons.nth(count - 2).click();
}
}

export async function getFirstSearchResultTitle(page: Page) {
const firstResultSelector = page.locator(
".usa-list--unstyled > li:first-child h2 a",
);
return await firstResultSelector.textContent();
}

export async function getLastSearchResultTitle(page: Page) {
const lastResultSelector = page.locator(
".usa-list--unstyled > li:last-child h2 a",
);
return await lastResultSelector.textContent();
}

// If descending, select the ascending variant
export async function selectOppositeSortOption(page: Page) {
const sortByDropdown = page.locator("#search-sort-by-select");
const currentValue = await sortByDropdown.inputValue();
let oppositeValue;

if (currentValue.includes("Asc")) {
oppositeValue = currentValue.replace("Asc", "Desc");
} else if (currentValue.includes("Desc")) {
oppositeValue = currentValue.replace("Desc", "Asc");
} else {
throw new Error(`Unexpected sort value: ${currentValue}`);
}

await sortByDropdown.selectOption(oppositeValue);
}

export async function waitForLoaderToBeHidden(page: Page) {
await page.waitForSelector(
".display-flex.flex-align-center.flex-justify-center.margin-bottom-15.margin-top-15",
{ state: "hidden" },
);
}

export async function getNumberOfOpportunitySearchResults(page: Page) {
await waitForLoaderToBeHidden(page);
const opportunitiesText = await page
.locator("h2.tablet-lg\\:grid-col-fill")
.textContent();
return opportunitiesText
? parseInt(opportunitiesText.replace(/\D/g, ""), 10)
: 0;
}
Loading