Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
yaaay
  • Loading branch information
aslilac committed Dec 19, 2024
commit a4d778eed50f2de2948682fc13d6d96c2639f535
9 changes: 2 additions & 7 deletions site/e2e/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@ import { findSessionToken, randomName } from "./helpers";
let currentOrgId: string;

export const setupApiCalls = async (page: Page) => {
try {
const token = await findSessionToken(page);
API.setSessionToken(token);
} catch {
// If this fails, we have an unauthenticated client.
}

API.setHost(`http://127.0.0.1:${coderPort}`);
const token = await findSessionToken(page);
API.setSessionToken(token);
};

export const getCurrentOrgId = async (): Promise<string> => {
Expand Down
28 changes: 24 additions & 4 deletions site/e2e/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as path from "node:path";
import { string } from "yup";

export const coderMain = path.join(__dirname, "../../enterprise/cmd/coder");

Expand All @@ -15,11 +16,30 @@ export const coderdPProfPort = 6062;

// The name of the organization that should be used by default when needed.
export const defaultOrganizationName = "coder";
export const defaultPassword = "SomeSecurePassword!";

// Credentials for the first user
export const username = "admin";
export const password = "SomeSecurePassword!";
export const email = "admin@coder.com";
// Credentials for users
export const users = {
admin: {
username: "admin",
password: defaultPassword,
email: "admin@coder.com",
},
auditor: {
username: "auditor",
password: defaultPassword,
email: "auditor@coder.com",
roles: ["Template Admin", "Auditor"],
},
user: {
username: "user",
password: defaultPassword,
email: "user@coder.com",
},
} satisfies Record<
string,
{ username: string; password: string; email: string; roles?: string[] }
>;

export const gitAuth = {
deviceProvider: "device",
Expand Down
89 changes: 71 additions & 18 deletions site/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import {
coderMain,
coderPort,
defaultOrganizationName,
email,
users,
license,
password,
premiumTestsRequired,
prometheusPort,
requireTerraformTests,
defaultPassword,
} from "./constants";
import { expectUrl } from "./expectUrl";
import {
Expand Down Expand Up @@ -62,40 +62,74 @@ export function requireTerraformProvisioner() {
}

type LoginOptions = {
username: string;
email: string;
password: string;
};

export async function login(page: Page, options?: LoginOptions) {
export async function login(page: Page, options: LoginOptions = users.admin) {
const ctx = page.context();
// biome-ignore lint/suspicious/noExplicitAny: shenanigans to make life easier
(ctx as any)[Symbol.for("currentUser")] = undefined;
await ctx.clearCookies();
await page.goto("/login");
await page.getByLabel("Email").fill(options?.email || email);
await page.getByLabel("Password").fill(options?.password || password);
await page.getByLabel("Email").fill(options.email);
await page.getByLabel("Password").fill(options.password);
await page.getByRole("button", { name: "Sign In" }).click();
await expectUrl(page).toHavePathName("/workspaces");
// biome-ignore lint/suspicious/noExplicitAny: shenanigans to make life easier
(ctx as any)[Symbol.for("currentUser")] = options;
}

export function currentUser(page: Page): LoginOptions {
const ctx = page.context();
// biome-ignore lint/suspicious/noExplicitAny: shenanigans to make life easier
const user = (ctx as any)[Symbol.for("currentUser")];

if (!user) {
throw new Error("page context does not have a user. did you call `login`?");
}

return user;
}

type CreateWorkspaceOptions = {
richParameters?: RichParameter[];
buildParameters?: WorkspaceBuildParameter[];
useExternalAuth?: boolean;
};

/**
* createWorkspace creates a workspace for a template. It does not wait for it
* to be running, but it does navigate to the page.
*/
export const createWorkspace = async (
page: Page,
templateName: string,
richParameters: RichParameter[] = [],
buildParameters: WorkspaceBuildParameter[] = [],
useExternalAuthProvider: string | undefined = undefined,
template: string | { organization: string; name: string },
options: CreateWorkspaceOptions = {},
): Promise<string> => {
await page.goto(`/templates/${templateName}/workspace`, {
const {
richParameters = [],
buildParameters = [],
useExternalAuth,
} = options;

const templatePath =
typeof template === "string"
? template
: `${template.organization}/${template.name}`;

await page.goto(`/templates/${templatePath}/workspace`, {
waitUntil: "domcontentloaded",
});
await expectUrl(page).toHavePathName(`/templates/${templateName}/workspace`);
await expectUrl(page).toHavePathName(`/templates/${templatePath}/workspace`);

const name = randomName();
await page.getByLabel("name").fill(name);

await fillParameters(page, richParameters, buildParameters);

if (useExternalAuthProvider !== undefined) {
if (useExternalAuth) {
// Create a new context for the popup which will be created when clicking the button
const popupPromise = page.waitForEvent("popup");

Expand All @@ -115,7 +149,9 @@ export const createWorkspace = async (

await page.getByTestId("form-submit").click();

await expectUrl(page).toHavePathName(`/@admin/${name}`);
const user = currentUser(page);

await expectUrl(page).toHavePathName(`/@${user.username}/${name}`);

await page.waitForSelector("[data-testid='build-status'] >> text=Running", {
state: "visible",
Expand Down Expand Up @@ -228,6 +264,12 @@ export const createTemplate = async (
const orgPicker = page.getByLabel("Belongs to *");
const organizationsEnabled = await orgPicker.isVisible();
if (organizationsEnabled) {
if (orgName !== defaultOrganizationName) {
throw new Error(
`No provisioners registered for ${orgName}, creating this template will fail`,
);
}

await orgPicker.click();
await page.getByText(orgName, { exact: true }).click();
}
Expand Down Expand Up @@ -673,8 +715,9 @@ const createTemplateVersionTar = async (
);
};

export const randomName = () => {
return randomUUID().slice(0, 8);
export const randomName = (annotation?: string) => {
const base = randomUUID().slice(0, 8);
return annotation ? `${annotation}-${base}` : base;
};

/**
Expand Down Expand Up @@ -1016,6 +1059,7 @@ type UserValues = {
username: string;
email: string;
password: string;
roles: string[];
};

export async function createUser(
Expand All @@ -1033,7 +1077,8 @@ export async function createUser(
const username = userValues.username ?? randomName();
const name = userValues.name ?? username;
const email = userValues.email ?? `${username}@coder.com`;
const password = userValues.password || "s3cure&password!";
const password = userValues.password || defaultPassword;
const roles = userValues.roles ?? [];

await page.getByLabel("Username").fill(username);
if (name) {
Expand All @@ -1050,10 +1095,18 @@ export async function createUser(
await expect(page.getByText("Successfully created user.")).toBeVisible();

await expect(page).toHaveTitle("Users - Coder");
await expect(page.locator("tr", { hasText: email })).toBeVisible();
const addedRow = page.locator("tr", { hasText: email });
await expect(addedRow).toBeVisible();

// Give them a role
await addedRow.getByLabel("Edit user roles").click();
for (const role of roles) {
await page.getByText(role, { exact: true }).click();
}
await page.mouse.click(10, 10); // close the popover by clicking outside of it

await page.goto(returnTo, { waitUntil: "domcontentloaded" });
return { name, username, email, password };
return { name, username, email, password, roles };
}

export async function createOrganization(page: Page): Promise<{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { expect, test } from "@playwright/test";
import { API } from "api/api";
import { Language } from "pages/CreateUserPage/CreateUserForm";
import { setupApiCalls } from "../api";
import * as constants from "../constants";
import { coderPort, license, premiumTestsRequired, users } from "../constants";
import { expectUrl } from "../expectUrl";
import { createUser } from "../helpers";

test("setup deployment", async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" });
await setupApiCalls(page);
API.setHost(`http://127.0.0.1:${coderPort}`);
const exists = await API.hasFirstUser();
// First user already exists, abort early. All tests execute this as a dependency,
// if you run multiple tests in the UI, this will fail unless we check this.
Expand All @@ -16,27 +16,35 @@ test("setup deployment", async ({ page }) => {
}

// Setup first user
await page.getByLabel(Language.usernameLabel).fill(constants.username);
await page.getByLabel(Language.emailLabel).fill(constants.email);
await page.getByLabel(Language.passwordLabel).fill(constants.password);
await page.getByLabel(Language.usernameLabel).fill(users.admin.username);
await page.getByLabel(Language.emailLabel).fill(users.admin.email);
await page.getByLabel(Language.passwordLabel).fill(users.admin.password);
await page.getByTestId("create").click();

await expectUrl(page).toHavePathName("/workspaces");

await page.getByTestId("button-select-template").isVisible();

for (const user of Object.values(users)) {
// Already created as first user
if (user.username === "admin") {
continue;
}

await createUser(page, user);
}

// Setup license
if (constants.premiumTestsRequired || constants.license) {
if (premiumTestsRequired || license) {
// Make sure that we have something that looks like a real license
expect(constants.license).toBeTruthy();
expect(constants.license.length).toBeGreaterThan(92); // the signature alone should be this long
expect(constants.license.split(".").length).toBe(3); // otherwise it's invalid
expect(license).toBeTruthy();
expect(license.length).toBeGreaterThan(92); // the signature alone should be this long
expect(license.split(".").length).toBe(3); // otherwise it's invalid

await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
await expect(page).toHaveTitle("License Settings - Coder");

await page.getByText("Add a license").click();
await page.getByRole("textbox").fill(constants.license);
await page.getByRole("textbox").fill(license);
await page.getByText("Upload License").click();

await expect(
Expand Down
81 changes: 81 additions & 0 deletions site/e2e/tests/auditLogs.oldspec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { expect, test } from "@playwright/test";
import {
createTemplate,
createWorkspace,
login,
requiresLicense,
} from "../helpers";
import { beforeCoderTest } from "../hooks";

test.beforeEach(async ({ page }) => {
beforeCoderTest(page);
await login(page);
});

test("inspecting and filtering audit logs", async ({ page }) => {
requiresLicense();

const userName = "admin";
// Do some stuff that should show up in the audit logs
const templateName = await createTemplate(page);
const workspaceName = await createWorkspace(page, templateName);

// Go to the audit history
await page.goto("/audit");

const loginMessage = `${userName} logged in`;
const startedWorkspaceMessage = `${userName} started workspace ${workspaceName}`;

// Make sure those things we did all actually show up
await expect(page.getByText(loginMessage).first()).toBeVisible();

await page.getByText("All actions").click();
await page.getByText("Create", { exact: true }).click();

await expect(
page.getByText(`${userName} created template ${templateName}`),
).toBeVisible();
await expect(
page.getByText(`${userName} created workspace ${workspaceName}`),
).toBeVisible();

// Make sure we can inspect the details of the log item
const createdWorkspace = page.locator(".MuiTableRow-root", {
hasText: `${userName} created workspace ${workspaceName}`,
});
await createdWorkspace.getByLabel("open-dropdown").click();
await expect(
createdWorkspace.getByText(`automatic_updates: "never"`),
).toBeVisible();
await expect(
createdWorkspace.getByText(`name: "${workspaceName}"`),
).toBeVisible();
await page.getByLabel("Clear search").click();
await expect(page.getByText("All actions")).toBeVisible();

await page.getByText("All actions").click();
await page.getByText("Start", { exact: true }).click();
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
await page.getByLabel("Clear search").click();
await expect(page.getByText("All actions")).toBeVisible();

// Filter by resource type
await page.getByText("All resource types").click();
const workspaceBuildsOption = page.getByText("Workspace Build");
await workspaceBuildsOption.scrollIntoViewIfNeeded({ timeout: 5000 });
await workspaceBuildsOption.click();
// Our workspace build should be visible
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
// Logins should no longer be visible
await expect(page.getByText(loginMessage)).not.toBeVisible();
await page.getByLabel("Clear search").click();
await expect(page.getByText("All resource types")).toBeVisible();

// Filter by action type
await page.getByText("All actions").click();
await page.getByText("Login").click();
// Logins should be visible
await expect(page.getByText(loginMessage).first()).toBeVisible();
// Our workspace build should no longer be visible
await expect(page.getByText(startedWorkspaceMessage)).not.toBeVisible();
});
Loading
Loading