Thanks to visit codestin.com
Credit goes to github.com

Skip to content

chore: allow signing in as non-admin users in e2e tests #15892

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 19, 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
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
27 changes: 23 additions & 4 deletions site/e2e/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,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 = "[email protected]";
// Credentials for users
export const users = {
admin: {
username: "admin",
password: defaultPassword,
email: "[email protected]",
},
auditor: {
username: "auditor",
password: defaultPassword,
email: "[email protected]",
roles: ["Template Admin", "Auditor"],
},
user: {
username: "user",
password: defaultPassword,
email: "[email protected]",
},
} satisfies Record<
string,
{ username: string; password: string; email: string; roles?: string[] }
>;

export const gitAuth = {
deviceProvider: "device",
Expand Down
95 changes: 81 additions & 14 deletions site/e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { type ChildProcess, exec, spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import * as fs from "node:fs";
import net from "node:net";
import path from "node:path";
import { Duplex } from "node:stream";
Expand All @@ -19,10 +18,12 @@ import {
coderMain,
coderPort,
defaultOrganizationName,
defaultPassword,
license,
premiumTestsRequired,
prometheusPort,
requireTerraformTests,
users,
} from "./constants";
import { expectUrl } from "./expectUrl";
import {
Expand Down Expand Up @@ -60,28 +61,75 @@ export function requireTerraformProvisioner() {
test.skip(!requireTerraformTests);
}

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

export async function login(page: Page, options: LoginOptions = users.admin) {
const ctx = page.context();
// biome-ignore lint/suspicious/noExplicitAny: reset the current user
(ctx as any)[Symbol.for("currentUser")] = undefined;
await ctx.clearCookies();
await page.goto("/login");
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: update once logged in
(ctx as any)[Symbol.for("currentUser")] = options;
}

export function currentUser(page: Page): LoginOptions {
const ctx = page.context();
// biome-ignore lint/suspicious/noExplicitAny: get the current user
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 @@ -101,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 @@ -214,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 @@ -659,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 @@ -1002,6 +1059,7 @@ type UserValues = {
username: string;
email: string;
password: string;
roles: string[];
};

export async function createUser(
Expand All @@ -1019,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 @@ -1036,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
2 changes: 1 addition & 1 deletion site/e2e/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import http from "node:http";
import type { BrowserContext, Page } from "@playwright/test";
import { coderPort, gitAuth } from "./constants";

export const beforeCoderTest = async (page: Page) => {
export const beforeCoderTest = (page: Page) => {
page.on("console", (msg) => console.info(`[onConsole] ${msg.text()}`));

page.on("request", (request) => {
Expand Down
8 changes: 2 additions & 6 deletions site/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ import {

export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT;

// This is where auth cookies are stored!
export const storageState = path.join(__dirname, ".auth.json");

// If running terraform tests, verify the requirements exist in the
// environment.
//
Expand Down Expand Up @@ -58,13 +55,12 @@ export default defineConfig({
projects: [
{
name: "testsSetup",
testMatch: /global.setup\.ts/,
testMatch: /setup\/.*\.spec\.ts/,
},
{
name: "tests",
testMatch: /.*\.spec\.ts/,
testMatch: /tests\/.*\.spec\.ts/,
dependencies: ["testsSetup"],
use: { storageState },
timeout: 30_000,
},
],
Expand Down
36 changes: 21 additions & 15 deletions site/e2e/global.setup.ts → site/e2e/setup/createUsers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +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 { expectUrl } from "./expectUrl";
import { storageState } from "./playwright.config";
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 @@ -17,28 +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.context().storageState({ path: storageState });

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
6 changes: 5 additions & 1 deletion site/e2e/tests/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import { test } from "@playwright/test";
import {
createTemplate,
createWorkspace,
login,
startAgent,
stopAgent,
stopWorkspace,
} from "../helpers";
import { beforeCoderTest } from "../hooks";

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

test("app", async ({ context, page }) => {
test.setTimeout(75_000);
Expand Down
Loading
Loading