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: generate simple auth tests #8709

Merged
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
4 changes: 4 additions & 0 deletions .github/workflows/platform-frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ jobs:
run: |
cp ../supabase/docker/.env.example ../.env
- name: Copy backend .env
run: |
cp ../backend/.env.example ../backend/.env
- name: Run docker compose
run: |
docker compose -f ../docker-compose.yml up -d
Expand Down
1 change: 1 addition & 0 deletions autogpt_platform/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"defaults"
],
"dependencies": {
"@faker-js/faker": "^9.2.0",
"@hookform/resolvers": "^3.9.1",
"@next/third-parties": "^15.0.3",
"@radix-ui/react-avatar": "^1.1.1",
Expand Down
8 changes: 4 additions & 4 deletions autogpt_platform/frontend/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { defineConfig, devices } from "@playwright/test";
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

import dotenv from "dotenv";
import path from "path";
dotenv.config({ path: path.resolve(__dirname, ".env") });
dotenv.config({ path: path.resolve(__dirname, "../backend/.env") });
/**
* See https://playwright.dev/docs/test-configuration.
*/
Expand Down
46 changes: 46 additions & 0 deletions autogpt_platform/frontend/src/tests/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { test, expect } from "./fixtures";
ntindle marked this conversation as resolved.
Show resolved Hide resolved

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 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();
});

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

// Expect to be on the home page
await 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");
});

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 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 loginPage.login(testUser.email, testUser.password);
await expect(page).toHaveURL("/");
await expect(page.getByText("Monitor")).toBeVisible();
});
});
18 changes: 18 additions & 0 deletions autogpt_platform/frontend/src/tests/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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 { LoginPage } from "../pages/login.page";

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

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

export { expect } from "@playwright/test";
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { test as base } from "@playwright/test";
import { LoginPage } from "../pages/login.page";

export const loginPageFixture = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});

// Export just the fixture function
export const createLoginPageFixture = async ({ page }, use) => {
await use(new LoginPage(page));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { faker } from "@faker-js/faker";

export type TestUser = {
email: string;
password: string;
id?: string;
};

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;
}

async function createTestUser(userData: TestUser): Promise<TestUser> {
const supabase = getSupabaseAdmin();

const { data: authUser, error: authError } = await supabase.auth.signUp({
email: userData.email,
password: userData.password,
});

if (authError) {
throw new Error(`Failed to create test user: ${authError.message}`);
}

return {
...userData,
id: authUser.user?.id,
};
}

async function deleteTestUser(userId: string) {
const supabase = getSupabaseAdmin();

try {
const { error } = await supabase.auth.admin.deleteUser(userId);

if (error) {
console.warn(`Warning: Failed to delete test user: ${error.message}`);
}
} catch (error) {
console.warn(
`Warning: Error during user cleanup: ${(error as Error).message}`,
);
}
}

function generateUserData(): TestUser {
return {
email: `test.${faker.string.uuid()}@example.com`,
password: faker.internet.password({ length: 12 }),
};
}

// Export just the fixture function
export const createTestUserFixture = async ({}, use) => {
let user: TestUser | null = null;

try {
const userData = generateUserData();
user = await createTestUser(userData);
await use(user);
} finally {
if (user?.id) {
await deleteTestUser(user.id);
}
}
};
51 changes: 51 additions & 0 deletions autogpt_platform/frontend/src/tests/pages/login.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Page } from "@playwright/test";

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

async login(email: string, password: string) {
console.log("Attempting login with:", { email, password }); // Debug log

// Fill email
const emailInput = this.page.getByPlaceholder("[email protected]");
await emailInput.waitFor({ state: "visible" });
await emailInput.fill(email);

// Fill password
const passwordInput = this.page.getByPlaceholder("password");
await passwordInput.waitFor({ state: "visible" });
await passwordInput.fill(password);

// Check terms
const termsCheckbox = this.page.getByLabel("I agree to the Terms of Use");
await termsCheckbox.waitFor({ state: "visible" });
await termsCheckbox.click();

// TODO: This is a workaround to wait for the page to load after filling the email and password
const emailInput2 = this.page.getByPlaceholder("[email protected]");
await emailInput2.waitFor({ state: "visible" });
await emailInput2.fill(email);

// Fill password
const passwordInput2 = this.page.getByPlaceholder("password");
await passwordInput2.waitFor({ state: "visible" });
await passwordInput2.fill(password);

// Wait for the button to be ready
const loginButton = this.page.getByRole("button", { name: "Log in" });
await loginButton.waitFor({ state: "visible" });

// Start waiting for navigation before clicking
const navigationPromise = this.page.waitForURL("/", { timeout: 60000 });

console.log("About to click login button"); // Debug log
await loginButton.click();

console.log("Waiting for navigation"); // Debug log
await navigationPromise;

console.log("Navigation complete, waiting for network idle"); // Debug log
await this.page.waitForLoadState("networkidle", { timeout: 60000 });
console.log("Login process complete"); // Debug log
}
}
2 changes: 1 addition & 1 deletion autogpt_platform/frontend/src/tests/title.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from "@playwright/test";
import { test, expect } from "./fixtures";

test("has title", async ({ page }) => {
await page.goto("/");
Expand Down
2 changes: 1 addition & 1 deletion autogpt_platform/frontend/src/tests/util.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from "@playwright/test";
import { test, expect } from "./fixtures";
import { setNestedProperty } from "../lib/utils";

const testCases = [
Expand Down
9 changes: 9 additions & 0 deletions autogpt_platform/frontend/src/tests/utils/user-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { faker } from "@faker-js/faker";

export function generateUser() {
return {
email: faker.internet.email(),
password: faker.internet.password(),
name: faker.person.fullName(),
};
}
5 changes: 5 additions & 0 deletions autogpt_platform/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,11 @@
resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz"
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==

"@faker-js/faker@^9.2.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.2.0.tgz#269ee3a5d2442e88e10d984e106028422bcb9551"
integrity sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==

"@floating-ui/core@^1.6.0":
version "1.6.7"
resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz"
Expand Down
Loading