Skip to content

Commit

Permalink
Expand Playwright tests
Browse files Browse the repository at this point in the history
Several new end-to-end tests have been introduced to expand the
confidence we have in the overall functioning of Towtruck, especially
considering that the application has grown significantly more complex.

Tests are now roughly scoped in their files according to the aspect of
the application being tested (e.g. logging in).
  • Loading branch information
danlivings-dxw committed Mar 7, 2025
1 parent 745ffa5 commit c7eb767
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 74 deletions.
100 changes: 100 additions & 0 deletions e2es/dashboard.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { test, expect } from "@playwright/test";
import { getAuthFile } from "./auth.js";

test.describe(() => {
test.use({ storageState: getAuthFile('test-user-1') });

test("shows expected info for a user with access", async ({ page, baseURL }) => {
await page.goto(`${baseURL}/orgs/test-org`);

await expect(page).toHaveTitle(/Towtruck/);

expect(await page.getByText("There are 3 repositories that Towtruck is tracking for test-org.").count()).toBe(1);

const tableHeadings = [
"Repository",
"Open issues count",
"Open bot PR count",
"Open PR count",
"Updated at",
"Most recent PR opened",
"Oldest open PR opened",
"Most recent issue opened",
"Oldest open issue opened"
];

for (const heading of tableHeadings) {
expect(await page.getByRole("columnheader", { name: heading }).count()).toBe(1);
}
});

test("sorting by column works as expected", async ({ page, baseURL }) => {
await page.goto(`${baseURL}/orgs/test-org`);

await testSortingForColumn(
{
name: "Open issues count",
topAscending: "govuk-blogs",
topDescending: "optionparser",
},
page
);

await testSortingForColumn(
{
name: "Open bot PR count",
topAscending: "optionparse",
topDescending: "govuk-blogs",
},
page
);

await testSortingForColumn(
{
name: "Open PR count",
topAscending: "optionparser",
topDescending: "php-missing",
},
page
);

await testSortingForColumn(
{
name: "Updated at",
topAscending: "optionparser",
topDescending: "govuk-blogs ",
},
page
);
});

const testSortingForColumn = async (
{ name, topAscending, topDescending },
page
) => {
await page.getByRole("link", { name, exact: true }).click();
await assertFirstDependencyRow(topAscending, page);

await page.getByRole("link", { name, exact: true }).click();
await assertFirstDependencyRow(topDescending, page);
};

const assertFirstDependencyRow = async (expectedFirstDependency, page) => {
const firstDependencyRow = page.getByRole("row").nth(1);
await expect(firstDependencyRow).toContainText(expectedFirstDependency);
};
});

test.describe(() => {
test.use({ storageState: getAuthFile('test-user-2') });

test("displays a 404 page for an unauthorised user", async ({ page, baseURL }) => {
await page.goto(`${baseURL}/orgs/test-org`);

await expect(page).toHaveTitle(/Towtruck/);

expect(await page.getByText(/The requested page .* could not be found./).count()).toBe(1);

expect(await page.getByText("There are 3 repositories that Towtruck is tracking for test-org.").count()).toBe(0);
});
});
45 changes: 45 additions & 0 deletions e2es/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { test, expect } from "@playwright/test";
import { getAuthFile } from "./auth.js";

test.describe(() => {
test.use({ storageState: getAuthFile('test-user-1') });

test("displays expected list of orgs for test-user-1 (member of one org using Towtruck)", async ({ page, baseURL }) => {
await page.goto(baseURL);

await expect(page).toHaveTitle(/Towtruck/);

expect(await page.getByText("There is 1 organisation using Towtruck that you are a member of.").count()).toBe(1);

expect(await page.getByRole("link", { name: "test-org", exact: true }).count()).toBe(1);
});
});

test.describe(() => {
test.use({ storageState: getAuthFile('test-user-2') });

test("displays expected list of orgs for test-user-2 (not a member of any orgs)", async ({ page, baseURL }) => {
await page.goto(baseURL);

await expect(page).toHaveTitle(/Towtruck/);

expect(await page.getByText("You are not a member of any organisations that are using Towtruck.").count()).toBe(1);

expect(await page.getByRole("link", { name: "test-org", exact: true }).count()).toBe(0);
});
});

test.describe(() => {
test.use({ storageState: getAuthFile('test-user-3') });

test("displays expected list of orgs for test-user-3 (member of multiple orgs, but only one using Towtruck)", async ({ page, baseURL }) => {
await page.goto(baseURL);

await expect(page).toHaveTitle(/Towtruck/);

expect(await page.getByText("There is 1 organisation using Towtruck that you are a member of.").count()).toBe(1);

expect(await page.getByRole("link", { name: "test-org", exact: true }).count()).toBe(1);
expect(await page.getByRole("link", { name: "another-org", exact: true }).count()).toBe(0);
});
});
50 changes: 50 additions & 0 deletions e2es/login.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { test, expect } from "@playwright/test";

test.describe(() => {
test("displays the login page when no user is logged in", async ({ page, baseURL }) => {
await page.goto(baseURL);

await expect(page).toHaveTitle(/Towtruck/);

expect(await page.getByText("To use Towtruck, you must login.").count()).toBe(1);

expect(await page.getByRole('button', { name: 'Login with GitHub' }).count()).toBe(1);
expect(await page.getByLabel("Username").count()).toBe(1);
expect(await page.getByLabel("Password").count()).toBe(1);

expect(await page.getByRole("link", { name: "test-org", exact: true }).count()).toBe(0);
});

test("a GitHub user cannot login through the username and password form", async ({ page, baseURL }) => {
await page.goto(baseURL);

await page.getByLabel('Username').fill('github-user');
await page.getByLabel('Password').fill('');

await page.locator('[type=submit]').click();

expect(await page.getByText('The username or password is incorrect.').count()).toBe(1);
});

test("a non-existent user cannot login", async ({ page, baseURL }) => {
await page.goto(baseURL);

await page.getByLabel('Username').fill('unknown-user');
await page.getByLabel('Password').fill('mypassword');

await page.locator('[type=submit]').click();

expect(await page.getByText('The username or password is incorrect.').count()).toBe(1);
});

test("a user cannot login with the wrong password", async ({ page, baseURL }) => {
await page.goto(baseURL);

await page.getByLabel('Username').fill('test-user-1');
await page.getByLabel('Password').fill('thewrongpassword');

await page.locator('[type=submit]').click();

expect(await page.getByText('The username or password is incorrect.').count()).toBe(1);
});
});
25 changes: 25 additions & 0 deletions e2es/logout.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { test, expect } from "@playwright/test";
import { getAuthFile } from "./auth.js";

test.describe(() => {
test.use({ storageState: getAuthFile('test-user-1') });

test("pressing the logout link logs the user out", async ({ page, baseURL }) => {
await page.goto(baseURL);

expect(await page.getByRole("link", { name: "Logout", exact: true }).count()).toBe(1);
await page.getByRole("link", { name: "Logout", exact: true }).click();

await page.waitForURL(baseURL);

expect(await page.getByText("To use Towtruck, you must login.").count()).toBe(1);
});
});

test.describe(() => {
test("there is no logout link when no user is logged in", async ({ page, baseURL }) => {
await page.goto(baseURL);

expect(await page.getByRole("link", { name: "Logout", exact: true }).count()).toBe(0);
});
});
74 changes: 0 additions & 74 deletions e2es/towtruck.spec.js

This file was deleted.

0 comments on commit c7eb767

Please sign in to comment.