chore(site): add e2e tests for groups (#12866)

This commit is contained in:
Bruno Quaresma 2024-04-04 21:56:28 -03:00 committed by GitHub
parent bc9ea61eb4
commit 3fbcdb0ddc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 240 additions and 26 deletions

45
site/e2e/api.ts Normal file
View File

@ -0,0 +1,45 @@
import type { Page } from "@playwright/test";
import * as API from "api/api";
import { coderPort } from "./constants";
import { findSessionToken, randomName } from "./helpers";
let currentOrgId: string;
export const setupApiCalls = async (page: Page) => {
const token = await findSessionToken(page);
API.setSessionToken(token);
API.setHost(`http://127.0.0.1:${coderPort}`);
};
export const getCurrentOrgId = async (): Promise<string> => {
if (currentOrgId) {
return currentOrgId;
}
const currentUser = await API.getAuthenticatedUser();
currentOrgId = currentUser.organization_ids[0];
return currentOrgId;
};
export const createUser = async (orgId: string) => {
const name = randomName();
const user = await API.createUser({
email: `${name}@coder.com`,
username: name,
password: "s3cure&password!",
login_type: "password",
disable_login: false,
organization_id: orgId,
});
return user;
};
export const createGroup = async (orgId: string) => {
const name = randomName();
const group = await API.createGroup(orgId, {
name,
display_name: `Display ${name}`,
avatar_url: "/emojis/1f60d.png",
quota_allowance: 0,
});
return group;
};

View File

@ -7,7 +7,6 @@ import capitalize from "lodash/capitalize";
import path from "path";
import * as ssh from "ssh2";
import { Duplex } from "stream";
import * as API from "api/api";
import type {
WorkspaceBuildParameter,
UpdateTemplateMeta,
@ -826,9 +825,3 @@ export async function openTerminalWindow(
return terminal;
}
export const setupApiCalls = async (page: Page) => {
const token = await findSessionToken(page);
API.setSessionToken(token);
API.setHost(`http://127.0.0.1:${coderPort}`);
};

View File

@ -0,0 +1,34 @@
import { test, expect } from "@playwright/test";
import {
createGroup,
createUser,
getCurrentOrgId,
setupApiCalls,
} from "../../api";
import { requiresEnterpriseLicense } from "../../helpers";
import { beforeCoderTest } from "../../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
test("add members", async ({ page, baseURL }) => {
requiresEnterpriseLicense();
await setupApiCalls(page);
const orgId = await getCurrentOrgId();
const group = await createGroup(orgId);
const numberOfMembers = 3;
const users = await Promise.all(
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
);
await page.goto(`${baseURL}/groups/${group.id}`, {
waitUntil: "domcontentloaded",
});
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
for (const user of users) {
await page.getByPlaceholder("User email or username").fill(user.username);
await page.getByRole("option", { name: user.email }).click();
await page.getByRole("button", { name: "Add user" }).click();
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
}
});

View File

@ -0,0 +1,32 @@
import { test, expect } from "@playwright/test";
import { createUser, getCurrentOrgId, setupApiCalls } from "../../api";
import { requiresEnterpriseLicense } from "../../helpers";
import { beforeCoderTest } from "../../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
const DEFAULT_GROUP_NAME = "Everyone";
test(`Every user should be automatically added to the default '${DEFAULT_GROUP_NAME}' group upon creation`, async ({
page,
baseURL,
}) => {
requiresEnterpriseLicense();
await setupApiCalls(page);
const orgId = await getCurrentOrgId();
const numberOfMembers = 3;
const users = await Promise.all(
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
);
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
await expect(page).toHaveTitle("Groups - Coder");
const groupRow = page.getByRole("row", { name: DEFAULT_GROUP_NAME });
await groupRow.click();
await expect(page).toHaveTitle(`${DEFAULT_GROUP_NAME} - Coder`);
for (const user of users) {
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
}
});

View File

@ -0,0 +1,30 @@
import { test, expect } from "@playwright/test";
import { randomName, requiresEnterpriseLicense } from "../../helpers";
import { beforeCoderTest } from "../../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
test("create group", async ({ page, baseURL }) => {
requiresEnterpriseLicense();
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
await expect(page).toHaveTitle("Groups - Coder");
await page.getByText("Create group").click();
await expect(page).toHaveTitle("Create Group - Coder");
const name = randomName();
const groupValues = {
name: name,
displayName: `Display Name for ${name}`,
avatarURL: "/emojis/1f60d.png",
};
await page.getByLabel("Name", { exact: true }).fill(groupValues.name);
await page.getByLabel("Display Name").fill(groupValues.displayName);
await page.getByLabel("Avatar URL").fill(groupValues.avatarURL);
await page.getByRole("button", { name: "Submit" }).click();
await expect(page).toHaveTitle(`${groupValues.displayName} - Coder`);
await expect(page.getByText(groupValues.displayName)).toBeVisible();
await expect(page.getByText("No members yet")).toBeVisible();
});

View File

@ -0,0 +1,23 @@
import { test, expect } from "@playwright/test";
import { createGroup, getCurrentOrgId, setupApiCalls } from "../../api";
import { requiresEnterpriseLicense } from "../../helpers";
import { beforeCoderTest } from "../../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
test("navigate to group page", async ({ page, baseURL }) => {
requiresEnterpriseLicense();
await setupApiCalls(page);
const orgId = await getCurrentOrgId();
const group = await createGroup(orgId);
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
await expect(page).toHaveTitle("Users - Coder");
await page.getByRole("link", { name: "Groups" }).click();
await expect(page).toHaveTitle("Groups - Coder");
const groupRow = page.getByRole("row", { name: group.display_name });
await groupRow.click();
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
});

View File

@ -0,0 +1,26 @@
import { test, expect } from "@playwright/test";
import { createGroup, getCurrentOrgId, setupApiCalls } from "../../api";
import { requiresEnterpriseLicense } from "../../helpers";
import { beforeCoderTest } from "../../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
test("remove group", async ({ page, baseURL }) => {
requiresEnterpriseLicense();
await setupApiCalls(page);
const orgId = await getCurrentOrgId();
const group = await createGroup(orgId);
await page.goto(`${baseURL}/groups/${group.id}`, {
waitUntil: "domcontentloaded",
});
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
await page.getByRole("button", { name: "Delete" }).click();
const dialog = page.getByTestId("dialog");
await dialog.getByLabel("Name of the group to delete").fill(group.name);
await dialog.getByRole("button", { name: "Delete" }).click();
await expect(page.getByText("Group deleted successfully.")).toBeVisible();
await expect(page).toHaveTitle("Groups - Coder");
});

View File

@ -0,0 +1,36 @@
import { test, expect } from "@playwright/test";
import * as API from "api/api";
import {
createGroup,
createUser,
getCurrentOrgId,
setupApiCalls,
} from "../../api";
import { requiresEnterpriseLicense } from "../../helpers";
import { beforeCoderTest } from "../../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
test("remove member", async ({ page, baseURL }) => {
requiresEnterpriseLicense();
await setupApiCalls(page);
const orgId = await getCurrentOrgId();
const [group, member] = await Promise.all([
createGroup(orgId),
createUser(orgId),
]);
await API.addMember(group.id, member.id);
await page.goto(`${baseURL}/groups/${group.id}`, {
waitUntil: "domcontentloaded",
});
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
const userRow = page.getByRole("row", { name: member.username });
await userRow.getByRole("button", { name: "More options" }).click();
const menu = page.locator("#more-options");
await menu.getByText("Remove").click({ timeout: 1_000 });
await expect(page.getByText("Member removed successfully.")).toBeVisible();
});

View File

@ -1,29 +1,21 @@
import { test, expect } from "@playwright/test";
import * as API from "api/api";
import { randomName, setupApiCalls } from "../../helpers";
import { createUser, getCurrentOrgId, setupApiCalls } from "../../api";
import { beforeCoderTest } from "../../hooks";
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
test("remove user", async ({ page, baseURL }) => {
await setupApiCalls(page);
const currentUser = await API.getAuthenticatedUser();
const name = randomName();
const user = await API.createUser({
email: `${name}@coder.com`,
username: name,
password: "s3cure&password!",
login_type: "password",
disable_login: false,
organization_id: currentUser.organization_ids[0],
});
const orgId = await getCurrentOrgId();
const user = await createUser(orgId);
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
await expect(page).toHaveTitle("Users - Coder");
const userRow = page.locator("tr", { hasText: user.email });
const userRow = page.getByRole("row", { name: user.email });
await userRow.getByRole("button", { name: "More options" }).click();
await userRow.getByText("Delete", { exact: false }).click();
const menu = page.locator("#more-options");
await menu.getByText("Delete").click();
const dialog = page.getByTestId("dialog");
await dialog.getByLabel("Name of the user to delete").fill(user.username);

View File

@ -1147,7 +1147,6 @@ export const patchGroup = async (
export const addMember = async (groupId: string, userId: string) => {
return patchGroup(groupId, {
name: "",
display_name: "",
add_users: [userId],
remove_users: [],
});

View File

@ -197,6 +197,7 @@ export const GroupPage: FC = () => {
onConfirm={async () => {
try {
await deleteGroupMutation.mutateAsync(groupId);
displaySuccess("Group deleted successfully.");
navigate("/groups");
} catch (error) {
displayError(getErrorMessage(error, "Failed to delete group."));

View File

@ -1,7 +1,6 @@
import GroupAdd from "@mui/icons-material/GroupAddOutlined";
import PersonAdd from "@mui/icons-material/PersonAddOutlined";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
import { type FC, Suspense } from "react";
import {
Link as RouterLink,
@ -43,9 +42,13 @@ export const UsersLayout: FC = () => {
</Button>
)}
{canCreateGroup && isTemplateRBACEnabled && (
<Link component={RouterLink} to="/groups/create">
<Button startIcon={<GroupAdd />}>Create group</Button>
</Link>
<Button
component={RouterLink}
startIcon={<GroupAdd />}
to="/groups/create"
>
Create group
</Button>
)}
</>
}