Skip to content

Commit c92f480

Browse files
authored
chore: add e2e test for org groups (coder#15853)
1 parent 50ff06c commit c92f480

File tree

8 files changed

+163
-25
lines changed

8 files changed

+163
-25
lines changed

site/e2e/api.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ export const getCurrentOrgId = async (): Promise<string> => {
2828
return currentOrgId;
2929
};
3030

31-
export const createUser = async (orgId: string) => {
31+
export const createUser = async (...orgIds: string[]) => {
3232
const name = randomName();
3333
const user = await API.createUser({
3434
email: `${name}@coder.com`,
3535
username: name,
3636
name: name,
3737
password: "s3cure&password!",
3838
login_type: "password",
39-
organization_ids: [orgId],
39+
organization_ids: orgIds,
4040
user_status: null,
4141
});
4242
return user;

site/e2e/helpers.ts

+22
Original file line numberDiff line numberDiff line change
@@ -1037,3 +1037,25 @@ export async function createUser(
10371037
await page.goto(returnTo, { waitUntil: "domcontentloaded" });
10381038
return { name, username, email, password };
10391039
}
1040+
1041+
export async function createOrganization(page: Page): Promise<{
1042+
name: string;
1043+
displayName: string;
1044+
description: string;
1045+
}> {
1046+
// Create a new organization to test
1047+
await page.goto("/organizations/new", { waitUntil: "domcontentloaded" });
1048+
const name = randomName();
1049+
await page.getByLabel("Slug").fill(name);
1050+
const displayName = `Org ${name}`;
1051+
await page.getByLabel("Display name").fill(displayName);
1052+
const description = `Org description ${name}`;
1053+
await page.getByLabel("Description").fill(description);
1054+
await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png");
1055+
await page.getByRole("button", { name: "Submit" }).click();
1056+
1057+
await expectUrl(page).toHavePathName(`/organizations/${name}`);
1058+
await expect(page.getByText("Organization created.")).toBeVisible();
1059+
1060+
return { name, displayName, description };
1061+
}
+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { expect, test } from "@playwright/test";
2+
import {
3+
createGroup,
4+
createOrganization,
5+
createUser,
6+
setupApiCalls,
7+
} from "../api";
8+
import { expectUrl } from "../expectUrl";
9+
import { randomName, requiresLicense } from "../helpers";
10+
import { beforeCoderTest } from "../hooks";
11+
12+
test.beforeEach(async ({ page }) => {
13+
await beforeCoderTest(page);
14+
await setupApiCalls(page);
15+
});
16+
17+
test("create group", async ({ page }) => {
18+
requiresLicense();
19+
20+
// Create a new organization
21+
const org = await createOrganization();
22+
await page.goto(`/organizations/${org.name}`);
23+
24+
// Navigate to groups page
25+
await page.getByText("Groups").click();
26+
await expect(page).toHaveTitle(`Groups - Org ${org.name} - Coder`);
27+
28+
// Create a new group
29+
await page.getByText("Create group").click();
30+
await expect(page).toHaveTitle("Create Group - Coder");
31+
const name = randomName();
32+
await page.getByLabel("Name", { exact: true }).fill(name);
33+
const displayName = `Group ${name}`;
34+
await page.getByLabel("Display Name").fill(displayName);
35+
await page.getByLabel("Avatar URL").fill("/emojis/1f60d.png");
36+
await page.getByRole("button", { name: "Submit" }).click();
37+
38+
await expectUrl(page).toHavePathName(
39+
`/organizations/${org.name}/groups/${name}`,
40+
);
41+
await expect(page).toHaveTitle(`${displayName} - Coder`);
42+
await expect(page.getByText("No members yet")).toBeVisible();
43+
await expect(page.getByText(displayName)).toBeVisible();
44+
45+
// Add a user to the group
46+
const personToAdd = await createUser(org.id);
47+
await page.getByPlaceholder("User email or username").fill(personToAdd.email);
48+
await page.getByRole("option", { name: personToAdd.email }).click();
49+
await page.getByRole("button", { name: "Add user" }).click();
50+
const addedRow = page.locator("tr", { hasText: personToAdd.email });
51+
await expect(addedRow).toBeVisible();
52+
53+
// Ensure we can't add a user who isn't in the org
54+
const otherOrg = await createOrganization();
55+
const personToReject = await createUser(otherOrg.id);
56+
await page
57+
.getByPlaceholder("User email or username")
58+
.fill(personToReject.email);
59+
await expect(page.getByText("No users found")).toBeVisible();
60+
61+
// Remove someone from the group
62+
await addedRow.getByLabel("More options").click();
63+
await page.getByText("Remove").click();
64+
await expect(addedRow).not.toBeVisible();
65+
66+
// Delete the group
67+
await page.getByRole("button", { name: "Delete" }).click();
68+
const dialog = page.getByTestId("dialog");
69+
await dialog.getByLabel("Name of the group to delete").fill(name);
70+
await dialog.getByRole("button", { name: "Delete" }).click();
71+
await expect(page.getByText("Group deleted successfully.")).toBeVisible();
72+
73+
await expectUrl(page).toHavePathName(`/organizations/${org.name}/groups`);
74+
await expect(page).toHaveTitle(`Groups - Org ${org.name} - Coder`);
75+
});
76+
77+
test("change quota settings", async ({ page }) => {
78+
requiresLicense();
79+
80+
// Create a new organization and group
81+
const org = await createOrganization();
82+
const group = await createGroup(org.id);
83+
84+
// Go to settings
85+
await page.goto(`/organizations/${org.name}/groups/${group.name}`);
86+
await page.getByRole("button", { name: "Settings" }).click();
87+
expectUrl(page).toHavePathName(
88+
`/organizations/${org.name}/groups/${group.name}/settings`,
89+
);
90+
91+
// Update Quota
92+
await page.getByLabel("Quota Allowance").fill("100");
93+
await page.getByRole("button", { name: "Submit" }).click();
94+
95+
// We should get sent back to the group page afterwards
96+
expectUrl(page).toHavePathName(
97+
`/organizations/${org.name}/groups/${group.name}`,
98+
);
99+
100+
// ...and that setting should persist if we go back
101+
await page.getByRole("button", { name: "Settings" }).click();
102+
await expect(page.getByLabel("Quota Allowance")).toHaveValue("100");
103+
});

site/e2e/tests/organizationMembers.spec.ts

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { expect, test } from "@playwright/test";
22
import { setupApiCalls } from "../api";
3-
import { expectUrl } from "../expectUrl";
4-
import { createUser, randomName, requiresLicense } from "../helpers";
3+
import { createOrganization, createUser, requiresLicense } from "../helpers";
54
import { beforeCoderTest } from "../hooks";
65

76
test.beforeEach(async ({ page }) => {
@@ -12,19 +11,12 @@ test.beforeEach(async ({ page }) => {
1211
test("add and remove organization member", async ({ page }) => {
1312
requiresLicense();
1413

15-
// Create a new organization to test
16-
await page.goto("/organizations/new", { waitUntil: "domcontentloaded" });
17-
const name = randomName();
18-
await page.getByLabel("Slug").fill(name);
19-
await page.getByLabel("Display name").fill(`Org ${name}`);
20-
await page.getByLabel("Description").fill(`Org description ${name}`);
21-
await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png");
22-
await page.getByRole("button", { name: "Submit" }).click();
14+
// Create a new organization
15+
const { displayName } = await createOrganization(page);
2316

2417
// Navigate to members page
25-
await expectUrl(page).toHavePathName(`/organizations/${name}`);
26-
await expect(page.getByText("Organization created.")).toBeVisible();
2718
await page.getByText("Members").click();
19+
await expect(page).toHaveTitle(`Members - ${displayName} - Coder`);
2820

2921
// Add a user to the org
3022
const personToAdd = await createUser(page);

site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@ export const GroupPage: FC = () => {
111111
{canUpdateGroup && (
112112
<Stack direction="row" spacing={2}>
113113
<Button
114+
role="button"
115+
component={RouterLink}
114116
startIcon={<SettingsOutlined />}
115117
to="settings"
116-
component={RouterLink}
117118
>
118119
Settings
119120
</Button>

site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ export const GroupsPage: FC = () => {
6969
return (
7070
<>
7171
<Helmet>
72-
<title>{pageTitle("Groups")}</title>
72+
<title>
73+
{pageTitle("Groups", organization.display_name || organization.name)}
74+
</title>
7375
</Helmet>
7476

7577
<Stack

site/src/pages/ManagementSettingsPage/OrganizationMembersPage.tsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import { Stack } from "components/Stack/Stack";
1717
import { useAuthenticated } from "contexts/auth/RequireAuth";
1818
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
1919
import { type FC, useState } from "react";
20+
import { Helmet } from "react-helmet-async";
2021
import { useMutation, useQuery, useQueryClient } from "react-query";
2122
import { useParams } from "react-router-dom";
23+
import { pageTitle } from "utils/page";
2224
import { OrganizationMembersPageView } from "./OrganizationMembersPageView";
2325

2426
const OrganizationMembersPage: FC = () => {
@@ -50,8 +52,7 @@ const OrganizationMembersPage: FC = () => {
5052
updateOrganizationMemberRoles(queryClient, organizationName),
5153
);
5254

53-
const { organizations } = useManagementSettings();
54-
const organization = organizations?.find((o) => o.name === organizationName);
55+
const { organization } = useManagementSettings();
5556
const permissionsQuery = useQuery(organizationPermissions(organization?.id));
5657

5758
const [memberToDelete, setMemberToDelete] =
@@ -62,8 +63,17 @@ const OrganizationMembersPage: FC = () => {
6263
return <Loader />;
6364
}
6465

66+
const helmet = organization && (
67+
<Helmet>
68+
<title>
69+
{pageTitle("Members", organization.display_name || organization.name)}
70+
</title>
71+
</Helmet>
72+
);
73+
6574
return (
6675
<>
76+
{helmet}
6777
<OrganizationMembersPageView
6878
allAvailableRoles={organizationRolesQuery.data}
6979
canEditMembers={permissions.editMembers}

site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { buildInfo } from "api/queries/buildInfo";
22
import { provisionerDaemonGroups } from "api/queries/organizations";
3-
import type { Organization } from "api/typesGenerated";
43
import { EmptyState } from "components/EmptyState/EmptyState";
54
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
65
import { useDashboard } from "modules/dashboard/useDashboard";
76
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
87
import type { FC } from "react";
8+
import { Helmet } from "react-helmet-async";
99
import { useQuery } from "react-query";
1010
import { useParams } from "react-router-dom";
11+
import { pageTitle } from "utils/page";
1112
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
1213

1314
const OrganizationProvisionersPage: FC = () => {
@@ -25,12 +26,19 @@ const OrganizationProvisionersPage: FC = () => {
2526
}
2627

2728
return (
28-
<OrganizationProvisionersPageView
29-
showPaywall={!entitlements.features.multiple_organizations.enabled}
30-
error={provisionersQuery.error}
31-
buildInfo={buildInfoQuery.data}
32-
provisioners={provisionersQuery.data}
33-
/>
29+
<>
30+
<Helmet>
31+
<title>
32+
{pageTitle("Members", organization.display_name || organization.name)}
33+
</title>
34+
</Helmet>
35+
<OrganizationProvisionersPageView
36+
showPaywall={!entitlements.features.multiple_organizations.enabled}
37+
error={provisionersQuery.error}
38+
buildInfo={buildInfoQuery.data}
39+
provisioners={provisionersQuery.data}
40+
/>
41+
</>
3442
);
3543
};
3644

0 commit comments

Comments
 (0)