Skip to content
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

feat(tests): add baseline utility for integration testing from frontend ui #8765

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,5 @@ ig*
.github_access_token
LICENSE.rtf
autogpt_platform/backend/settings.py
/.auth
/autogpt_platform/frontend/.auth
4 changes: 3 additions & 1 deletion autogpt_platform/frontend/src/app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ export default function PrivatePage() {
return (
<div className="mx-auto max-w-3xl md:py-8">
<div className="flex items-center justify-between">
<p>Hello {user.email}</p>
<p>
Hello <span data-testid="profile-email">{user.email}</span>
</p>
<Button onClick={() => supabase.auth.signOut()}>
<LogOutIcon className="mr-1.5 size-4" />
Log out
Expand Down
29 changes: 14 additions & 15 deletions autogpt_platform/frontend/src/tests/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
import { test, expect } from "./fixtures";
// auth.spec.ts
import { test } from "./fixtures";

test.describe("Authentication", () => {
test("user can login successfully", async ({ page, loginPage, testUser }) => {
await page.goto("/login"); // Make sure we're on the login page
await page.goto("/login");
await loginPage.login(testUser.email, testUser.password);
// expect to be redirected to the home page
await expect(page).toHaveURL("/");
// expect to see the Monitor text
await expect(page.getByText("Monitor")).toBeVisible();
await test.expect(page).toHaveURL("/");
await test.expect(page.getByText("Monitor")).toBeVisible();
});

test("user can logout successfully", async ({
page,
loginPage,
testUser,
}) => {
await page.goto("/login"); // Make sure we're on the login page
await page.goto("/login");
await loginPage.login(testUser.email, testUser.password);

// Expect to be on the home page
await expect(page).toHaveURL("/");
await test.expect(page).toHaveURL("/");

// Click on the user menu
await page.getByRole("button", { name: "CN" }).click();
// Click on the logout menu item
await page.getByRole("menuitem", { name: "Log out" }).click();
// Expect to be redirected to the login page
await expect(page).toHaveURL("/login");

await test.expect(page).toHaveURL("/login");
});

test("login in, then out, then in again", async ({
page,
loginPage,
testUser,
}) => {
await page.goto("/login"); // Make sure we're on the login page
await page.goto("/login");
await loginPage.login(testUser.email, testUser.password);
await page.goto("/");
await page.getByRole("button", { name: "CN" }).click();
await page.getByRole("menuitem", { name: "Log out" }).click();
await expect(page).toHaveURL("/login");
await test.expect(page).toHaveURL("/login");
await loginPage.login(testUser.email, testUser.password);
await expect(page).toHaveURL("/");
await expect(page.getByText("Monitor")).toBeVisible();
await test.expect(page).toHaveURL("/");
await test.expect(page.getByText("Monitor")).toBeVisible();
});
});
107 changes: 99 additions & 8 deletions autogpt_platform/frontend/src/tests/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,109 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { test as base } from "@playwright/test";
import { createTestUserFixture } from "./test-user.fixture";
import { createLoginPageFixture } from "./login-page.fixture";
import type { TestUser } from "./test-user.fixture";
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { faker } from "@faker-js/faker";
import fs from "fs";
import path from "path";
import { TestUser } from "./test-user.fixture";
import { LoginPage } from "../pages/login.page";

type Fixtures = {
// Extend both worker state and test-specific fixtures
type WorkerFixtures = {
workerAuth: TestUser;
};

type TestFixtures = {
testUser: TestUser;
loginPage: LoginPage;
};

// Combine fixtures
export const test = base.extend<Fixtures>({
testUser: createTestUserFixture,
loginPage: createLoginPageFixture,
let supabase: SupabaseClient;

function getSupabaseAdmin() {
if (!supabase) {
supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
},
);
}
return supabase;
}

export const test = base.extend<TestFixtures, WorkerFixtures>({
// Define the worker-level fixture that creates and manages worker-specific auth
workerAuth: [
async ({}, use, workerInfo) => {
const workerId = workerInfo.workerIndex;
const fileName = path.resolve(
process.cwd(),
`.auth/worker-${workerId}.json`,
);

// Create directory if it doesn't exist
const dirPath = path.dirname(fileName);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}

let auth: TestUser;
if (fs.existsSync(fileName)) {
auth = JSON.parse(fs.readFileSync(fileName, "utf-8"));
} else {
// Generate new worker-specific test user
auth = {
email: `test.worker.${workerId}.${Date.now()}@example.com`,
password: faker.internet.password({ length: 12 }),
};

const supabase = getSupabaseAdmin();
const {
data: { user },
error: signUpError,
} = await supabase.auth.signUp({
email: auth.email,
password: auth.password,
});

if (signUpError) {
throw signUpError;
}

auth.id = user?.id;
fs.writeFileSync(fileName, JSON.stringify(auth));
}

await use(auth);

// Cleanup code is commented out to preserve test users during development
ntindle marked this conversation as resolved.
Show resolved Hide resolved
/*
if (workerInfo.project.metadata.teardown) {
if (auth.id) {
await deleteTestUser(auth.id);
}
if (fs.existsSync(fileName)) {
fs.unlinkSync(fileName);
}
}
*/
},
{ scope: "worker" },
],

// Define the test-level fixture that provides access to the worker auth
testUser: async ({ workerAuth }, use) => {
await use(workerAuth);
},

// Update login page fixture to use worker auth by default
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});

export { expect } from "@playwright/test";
15 changes: 15 additions & 0 deletions autogpt_platform/frontend/src/tests/pages/base.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Page } from "@playwright/test";
import { NavBar } from "./navbar.page";

export class BasePage {
readonly navbar: NavBar;

constructor(protected page: Page) {
this.navbar = new NavBar(page);
}

async waitForPageLoad() {
// Common page load waiting logic
await this.page.waitForLoadState("networkidle", { timeout: 10000 });
}
}
51 changes: 51 additions & 0 deletions autogpt_platform/frontend/src/tests/pages/navbar.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Page } from "@playwright/test";

export class NavBar {
constructor(private page: Page) {}

async clickProfileLink() {
// await this.page.getByTestId("profile-link").click();

await this.page.getByRole("button", { name: "CN" }).click();
await this.page.getByRole("menuitem", { name: "Profile" }).click();
}

async clickMonitorLink() {
await this.page.getByTestId("monitor-link").click();
}

async clickBuildLink() {
await this.page.getByTestId("build-link").click();
}

async clickMarketplaceLink() {
await this.page.getByTestId("marketplace-link").click();
}
ntindle marked this conversation as resolved.
Show resolved Hide resolved

async getUserMenuButton() {
return this.page.getByRole("button", { name: "CN" });
}

async clickUserMenu() {
await (await this.getUserMenuButton()).click();
}

async logout() {
await this.clickUserMenu();
await this.page.getByRole("menuitem", { name: "Log out" }).click();
}

async isLoggedIn(): Promise<boolean> {
try {
await (
await this.getUserMenuButton()
).waitFor({
state: "visible",
timeout: 5000,
});
return true;
} catch {
return false;
}
}
}
38 changes: 38 additions & 0 deletions autogpt_platform/frontend/src/tests/pages/profile.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Page } from "@playwright/test";
import { BasePage } from "./base.page";

export class ProfilePage extends BasePage {
constructor(page: Page) {
super(page);
}

async getDisplayedEmail(): Promise<string> {
await this.waitForPageToLoad();
const email = await this.page.getByTestId("profile-email").textContent();
if (!email) {
throw new Error("Email not found");
}
return email;
}

async isLoaded(): Promise<boolean> {
try {
await this.waitForPageToLoad();
return true;
} catch (error) {
console.error("Error loading profile page", error);
return false;
}
}

private async waitForPageToLoad(): Promise<void> {
await this.page.waitForLoadState("networkidle", { timeout: 60_000 });

await this.page.getByTestId("profile-email").waitFor({
state: "visible",
timeout: 60_000,
});

await this.page.waitForLoadState("networkidle", { timeout: 60_000 });
}
}
52 changes: 52 additions & 0 deletions autogpt_platform/frontend/src/tests/profile.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// profile.spec.ts
import { test } from "./fixtures";
import { ProfilePage } from "./pages/profile.page";

test.describe("Profile", () => {
let profilePage: ProfilePage;

test.beforeEach(async ({ page, loginPage, testUser }) => {
profilePage = new ProfilePage(page);

// Start each test with login using worker auth
await page.goto("/login");
await loginPage.login(testUser.email, testUser.password);
await test.expect(page).toHaveURL("/");
});

test("user can view their profile information", async ({
page,
testUser,
}) => {
await profilePage.navbar.clickProfileLink();
// sleep for 10 seconds to allow page to load due to bug in our system
aarushik93 marked this conversation as resolved.
Show resolved Hide resolved
await page.waitForTimeout(10000);
await page.reload();
await test.expect(profilePage.isLoaded()).resolves.toBeTruthy();
await test.expect(page).toHaveURL(new RegExp("/profile"));

// Verify email matches test worker's email
const displayedEmail = await profilePage.getDisplayedEmail();
test.expect(displayedEmail).toBe(testUser.email);
});

test("profile navigation is accessible from navbar", async ({ page }) => {
await profilePage.navbar.clickProfileLink();
await test.expect(page).toHaveURL(new RegExp("/profile"));
await test.expect(profilePage.isLoaded()).resolves.toBeTruthy();
});

test("profile displays user Credential providers", async ({ page }) => {
await profilePage.navbar.clickProfileLink();

// await test
// .expect(page.getByTestId("profile-section-personal"))
// .toBeVisible();
// await test
// .expect(page.getByTestId("profile-section-settings"))
// .toBeVisible();
// await test
// .expect(page.getByTestId("profile-section-security"))
// .toBeVisible();
});
ntindle marked this conversation as resolved.
Show resolved Hide resolved
});
Loading