mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
fix(frontend): auth e2e tests (#10312)
This pull request introduces extensive updates to the frontend testing
infrastructure, focusing on Playwright-based testing for user
authentication flows. Key changes include the addition of a global setup
for creating test users, new utilities for managing test user pools, and
expanded test coverage for signup and authentication scenarios.
### Testing Infrastructure Enhancements:
* **Global Setup for Tests**:
- Added `globalSetup` in `playwright.config.ts` to create test users
before all tests run. This ensures consistent test data across test
suites. (`autogpt_platform/frontend/playwright.config.ts`,
[autogpt_platform/frontend/playwright.config.tsR16-R17](diffhunk://#diff-27484f7f20f2eb1aeb289730a440f3a126fa825a7b3fae1f9ed19e217c4f2e40R16-R17))
- Implemented `global-setup.ts` to handle test user creation and save
user pools to the file system. Includes fallback for reusing existing
user pools if available.
(`autogpt_platform/frontend/src/tests/global-setup.ts`,
[autogpt_platform/frontend/src/tests/global-setup.tsR1-R43](diffhunk://#diff-3a8141beba2a6117e0eb721c35b39acc168a8f913ee625ce056c6fab5ac3b192R1-R43))
* **Test User Management Utilities**:
- Added functions in `auth.ts` to create, save, load, and clean up test
users. Supports batch creation and file-based persistence for user
pools. (`autogpt_platform/frontend/src/tests/utils/auth.ts`,
[autogpt_platform/frontend/src/tests/utils/auth.tsR1-R190](diffhunk://#diff-198b5d07aa72d50c153a70ecdfdc4bacc408c2d638c90d858f40d0183549973bR1-R190))
- Enhanced `user-generator.ts` to generate individual or multiple test
users with customizable options.
(`autogpt_platform/frontend/src/tests/utils/user-generator.ts`,
[autogpt_platform/frontend/src/tests/utils/user-generator.tsR2-R41](diffhunk://#diff-a7cb4f403a4cf3605ed1046b0263412205e56e51b16052a9da1e8db9bf34b940R2-R41))
### Expanded Test Coverage:
* **Signup Flow Tests**:
- Added comprehensive tests for signup functionality, including
successful signup, form validation, custom credentials, and duplicate
email handling. (`autogpt_platform/frontend/src/tests/signup.spec.ts`,
[autogpt_platform/frontend/src/tests/signup.spec.tsR1-R113](diffhunk://#diff-d1baa54deff7f3b1eedefd6cec5619ae8edd872d361ef57b6c32998ed22d6661R1-R113))
- Developed `signup.ts` utility functions to automate signup processes
and validate form behavior.
(`autogpt_platform/frontend/src/tests/utils/signup.ts`,
[autogpt_platform/frontend/src/tests/utils/signup.tsR1-R184](diffhunk://#diff-cb05d73a6bd7a129759b0b3382825e90cde561a42fc85b6a25777f6bd2f84511R1-R184))
* **Authentication Utilities**:
- Introduced `SigninUtils` in `signin.ts` for login, logout, and
authentication cycle testing. Provides reusable methods for verifying
user states. (`autogpt_platform/frontend/src/tests/utils/signin.ts`,
[autogpt_platform/frontend/src/tests/utils/signin.tsR1-R94](diffhunk://#diff-7cfec955c159d69f51ba9fcca7d979be090acd6fe246b125551d60192d697d98R1-R94))
### Minor Updates:
* Added environment variable `BROWSER_TYPE` to CI workflow for
browser-specific Playwright tests.
(`.github/workflows/platform-frontend-ci.yml`,
[.github/workflows/platform-frontend-ci.ymlR215-R216](diffhunk://#diff-29396f5dccefac146b71bed295fdbb790b17fda6a5ce2e9f4f8abe80eb14a527R215-R216))
These changes collectively improve the robustness and maintainability of
the frontend testing framework, enabling more reliable and scalable
testing of user authentication features.
### Checklist 📋
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Validated all authentication tests, and they are working
This commit is contained in:
6
.github/workflows/platform-frontend-ci.yml
vendored
6
.github/workflows/platform-frontend-ci.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
needs: setup
|
||||
# Only run on dev branch pushes or PRs targeting dev
|
||||
if: github.ref == 'refs/heads/dev' || github.base_ref == 'dev'
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -212,6 +212,8 @@ jobs:
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: pnpm test:no-build --project=${{ matrix.browser }}
|
||||
env:
|
||||
BROWSER_TYPE: ${{ matrix.browser }}
|
||||
|
||||
- name: Print Final Docker Compose logs
|
||||
if: always()
|
||||
|
||||
@@ -13,6 +13,8 @@ dotenv.config({ path: path.resolve(__dirname, "../backend/.env") });
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./src/tests",
|
||||
/* Global setup file that runs before all tests */
|
||||
globalSetup: "./src/tests/global-setup.ts",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
|
||||
43
autogpt_platform/frontend/src/tests/global-setup.ts
Normal file
43
autogpt_platform/frontend/src/tests/global-setup.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { FullConfig } from "@playwright/test";
|
||||
import { createTestUsers, saveUserPool, loadUserPool } from "./utils/auth";
|
||||
|
||||
/**
|
||||
* Global setup function that runs before all tests
|
||||
* Creates test users and saves them to file system
|
||||
*/
|
||||
async function globalSetup(config: FullConfig) {
|
||||
console.log("🚀 Starting global test setup...");
|
||||
|
||||
try {
|
||||
const existingUserPool = await loadUserPool();
|
||||
|
||||
if (existingUserPool && existingUserPool.users.length > 0) {
|
||||
console.log(
|
||||
`♻️ Found existing user pool with ${existingUserPool.users.length} users`,
|
||||
);
|
||||
console.log("✅ Using existing user pool");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test users using signup page
|
||||
const numberOfUsers = (config.workers || 1) + 3; // workers + buffer
|
||||
console.log(`👥 Creating ${numberOfUsers} test users via signup...`);
|
||||
|
||||
const users = await createTestUsers(numberOfUsers);
|
||||
|
||||
if (users.length === 0) {
|
||||
throw new Error("Failed to create any test users");
|
||||
}
|
||||
|
||||
// Save user pool
|
||||
await saveUserPool(users);
|
||||
|
||||
console.log("✅ Global setup completed successfully!");
|
||||
console.log(`📊 Created ${users.length} test users via signup page`);
|
||||
} catch (error) {
|
||||
console.error("❌ Global setup failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
113
autogpt_platform/frontend/src/tests/signup.spec.ts
Normal file
113
autogpt_platform/frontend/src/tests/signup.spec.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { test, expect } from "./fixtures";
|
||||
import {
|
||||
signupTestUser,
|
||||
validateSignupForm,
|
||||
generateTestEmail,
|
||||
generateTestPassword,
|
||||
} from "./utils/signup";
|
||||
|
||||
test.describe("Signup Flow", () => {
|
||||
test("user can signup successfully", async ({ page }) => {
|
||||
console.log("🧪 Testing user signup flow...");
|
||||
|
||||
try {
|
||||
const testUser = await signupTestUser(page);
|
||||
|
||||
// Verify user was created
|
||||
expect(testUser.email).toBeTruthy();
|
||||
expect(testUser.password).toBeTruthy();
|
||||
expect(testUser.createdAt).toBeTruthy();
|
||||
|
||||
// Verify we're on marketplace and authenticated
|
||||
await expect(page).toHaveURL("/marketplace");
|
||||
await expect(
|
||||
page.getByText(
|
||||
"Bringing you AI agents designed by thinkers from around the world",
|
||||
),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("profile-popout-menu-trigger"),
|
||||
).toBeVisible();
|
||||
|
||||
console.log(`✅ User successfully signed up: ${testUser.email}`);
|
||||
} catch (error) {
|
||||
console.error("❌ Signup test failed:", error);
|
||||
}
|
||||
});
|
||||
|
||||
test("signup form validation works", async ({ page }) => {
|
||||
console.log("🧪 Testing signup form validation...");
|
||||
|
||||
await validateSignupForm(page);
|
||||
|
||||
// Additional validation tests
|
||||
await page.goto("/signup");
|
||||
|
||||
// Test with mismatched passwords
|
||||
console.log("❌ Testing mismatched passwords...");
|
||||
await page.getByPlaceholder("m@example.com").fill(generateTestEmail());
|
||||
const passwordInputs = page.getByTitle("Password");
|
||||
await passwordInputs.nth(0).fill("password1");
|
||||
await passwordInputs.nth(1).fill("password2");
|
||||
await page.getByRole("checkbox").click();
|
||||
await page.getByRole("button", { name: "Sign up" }).click();
|
||||
|
||||
// Should still be on signup page
|
||||
await expect(page).toHaveURL(/\/signup/);
|
||||
console.log("✅ Mismatched passwords correctly blocked");
|
||||
});
|
||||
|
||||
test("user can signup with custom credentials", async ({ page }) => {
|
||||
console.log("🧪 Testing signup with custom credentials...");
|
||||
|
||||
try {
|
||||
const customEmail = generateTestEmail();
|
||||
const customPassword = generateTestPassword();
|
||||
|
||||
const testUser = await signupTestUser(page, customEmail, customPassword);
|
||||
|
||||
// Verify correct credentials were used
|
||||
expect(testUser.email).toBe(customEmail);
|
||||
expect(testUser.password).toBe(customPassword);
|
||||
|
||||
// Verify successful signup
|
||||
await expect(page).toHaveURL("/marketplace");
|
||||
await expect(
|
||||
page.getByTestId("profile-popout-menu-trigger"),
|
||||
).toBeVisible();
|
||||
|
||||
console.log(`✅ Custom credentials signup worked: ${testUser.email}`);
|
||||
} catch (error) {
|
||||
console.error("❌ Custom credentials signup test failed:", error);
|
||||
}
|
||||
});
|
||||
|
||||
test("user can signup with existing email handling", async ({ page }) => {
|
||||
console.log("🧪 Testing duplicate email handling...");
|
||||
|
||||
try {
|
||||
const testEmail = generateTestEmail();
|
||||
const testPassword = generateTestPassword();
|
||||
|
||||
// First signup
|
||||
console.log(`👤 First signup attempt: ${testEmail}`);
|
||||
const firstUser = await signupTestUser(page, testEmail, testPassword);
|
||||
|
||||
expect(firstUser.email).toBe(testEmail);
|
||||
console.log("✅ First signup successful");
|
||||
|
||||
// Second signup attempt with same email should handle gracefully
|
||||
console.log(`👤 Second signup attempt: ${testEmail}`);
|
||||
try {
|
||||
await signupTestUser(page, testEmail, testPassword);
|
||||
console.log("ℹ️ Second signup handled gracefully");
|
||||
} catch (_error) {
|
||||
console.log("ℹ️ Second signup rejected as expected");
|
||||
}
|
||||
|
||||
console.log("✅ Duplicate email handling test completed");
|
||||
} catch (error) {
|
||||
console.error("❌ Duplicate email handling test failed:", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
184
autogpt_platform/frontend/src/tests/utils/auth.ts
Normal file
184
autogpt_platform/frontend/src/tests/utils/auth.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { chromium, webkit } from "@playwright/test";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { signupTestUser } from "./signup";
|
||||
|
||||
export interface TestUser {
|
||||
email: string;
|
||||
password: string;
|
||||
id?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface UserPool {
|
||||
users: TestUser[];
|
||||
createdAt: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
// Using Playwright MCP server tools for browser automation
|
||||
// No need to manage browser instances manually
|
||||
|
||||
/**
|
||||
* Create a new test user through signup page using Playwright MCP server
|
||||
* @param email - User email (optional, will generate if not provided)
|
||||
* @param password - User password (optional, will generate if not provided)
|
||||
* @param ignoreOnboarding - Skip onboarding and go to marketplace (default: true)
|
||||
* @returns Promise<TestUser> - Created user object
|
||||
*/
|
||||
export async function createTestUser(
|
||||
email?: string,
|
||||
password?: string,
|
||||
ignoreOnboarding: boolean = true,
|
||||
): Promise<TestUser> {
|
||||
const userEmail = email || faker.internet.email();
|
||||
const userPassword = password || faker.internet.password({ length: 12 });
|
||||
|
||||
try {
|
||||
const browserType = process.env.BROWSER_TYPE || "chromium";
|
||||
|
||||
const browser =
|
||||
browserType === "webkit"
|
||||
? await webkit.launch({ headless: true })
|
||||
: await chromium.launch({ headless: true });
|
||||
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
const testUser = await signupTestUser(
|
||||
page,
|
||||
userEmail,
|
||||
userPassword,
|
||||
ignoreOnboarding,
|
||||
);
|
||||
return testUser;
|
||||
} finally {
|
||||
await page.close();
|
||||
await context.close();
|
||||
await browser.close();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error creating test user ${userEmail}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create multiple test users
|
||||
* @param count - Number of users to create
|
||||
* @returns Promise<TestUser[]> - Array of created users
|
||||
*/
|
||||
export async function createTestUsers(count: number): Promise<TestUser[]> {
|
||||
console.log(`👥 Creating ${count} test users...`);
|
||||
|
||||
const users: TestUser[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
try {
|
||||
const user = await createTestUser();
|
||||
users.push(user);
|
||||
console.log(`✅ Created user ${i + 1}/${count}: ${user.email}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to create user ${i + 1}/${count}:`, error);
|
||||
// Continue creating other users even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🎉 Successfully created ${users.length}/${count} test users`);
|
||||
return users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user pool to file system
|
||||
* @param users - Array of users to save
|
||||
* @param filePath - Path to save the file (optional)
|
||||
*/
|
||||
export async function saveUserPool(
|
||||
users: TestUser[],
|
||||
filePath?: string,
|
||||
): Promise<void> {
|
||||
const defaultPath = path.resolve(process.cwd(), ".auth", "user-pool.json");
|
||||
const finalPath = filePath || defaultPath;
|
||||
|
||||
// Ensure .auth directory exists
|
||||
const dirPath = path.dirname(finalPath);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
const userPool: UserPool = {
|
||||
users,
|
||||
createdAt: new Date().toISOString(),
|
||||
version: "1.0.0",
|
||||
};
|
||||
|
||||
try {
|
||||
fs.writeFileSync(finalPath, JSON.stringify(userPool, null, 2));
|
||||
console.log(`✅ Successfully saved user pool to: ${finalPath}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to save user pool to ${finalPath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load user pool from file system
|
||||
* @param filePath - Path to load from (optional)
|
||||
* @returns Promise<UserPool | null> - Loaded user pool or null if not found
|
||||
*/
|
||||
export async function loadUserPool(
|
||||
filePath?: string,
|
||||
): Promise<UserPool | null> {
|
||||
const defaultPath = path.resolve(process.cwd(), ".auth", "user-pool.json");
|
||||
const finalPath = filePath || defaultPath;
|
||||
|
||||
console.log(`📖 Loading user pool from: ${finalPath}`);
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(finalPath)) {
|
||||
console.log(`⚠️ User pool file not found: ${finalPath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const fileContent = fs.readFileSync(finalPath, "utf-8");
|
||||
const userPool: UserPool = JSON.parse(fileContent);
|
||||
|
||||
console.log(
|
||||
`✅ Successfully loaded ${userPool.users.length} users from: ${finalPath}`,
|
||||
);
|
||||
console.log(`📅 User pool created at: ${userPool.createdAt}`);
|
||||
console.log(`🔖 User pool version: ${userPool.version}`);
|
||||
|
||||
return userPool;
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to load user pool from ${finalPath}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all test users from a pool
|
||||
* Note: When using signup page method, cleanup removes the user pool file
|
||||
* @param filePath - Path to load from (optional)
|
||||
*/
|
||||
export async function cleanupTestUsers(filePath?: string): Promise<void> {
|
||||
const defaultPath = path.resolve(process.cwd(), ".auth", "user-pool.json");
|
||||
const finalPath = filePath || defaultPath;
|
||||
|
||||
console.log(`🧹 Cleaning up test users...`);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(finalPath)) {
|
||||
fs.unlinkSync(finalPath);
|
||||
console.log(`✅ Deleted user pool file: ${finalPath}`);
|
||||
} else {
|
||||
console.log(`⚠️ No user pool file found to cleanup`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to cleanup user pool:`, error);
|
||||
}
|
||||
|
||||
console.log(`🎉 Cleanup completed`);
|
||||
}
|
||||
94
autogpt_platform/frontend/src/tests/utils/signin.ts
Normal file
94
autogpt_platform/frontend/src/tests/utils/signin.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { LoginPage } from "../pages/login.page";
|
||||
import { TestUser } from "../fixtures/test-user.fixture";
|
||||
|
||||
/**
|
||||
* Utility functions for signin/authentication tests
|
||||
*/
|
||||
export class SigninUtils {
|
||||
constructor(
|
||||
private page: Page,
|
||||
private loginPage: LoginPage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Perform login and verify success
|
||||
*/
|
||||
async loginAndVerify(testUser: TestUser): Promise<void> {
|
||||
console.log(`🔐 Logging in as: ${testUser.email}`);
|
||||
|
||||
await this.page.goto("/login");
|
||||
await this.loginPage.login(testUser.email, testUser.password);
|
||||
|
||||
// Verify we're on marketplace
|
||||
await this.page.waitForURL("/marketplace");
|
||||
|
||||
// Verify profile menu is visible (user is authenticated)
|
||||
await this.page.getByTestId("profile-popout-menu-trigger").waitFor({
|
||||
state: "visible",
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
console.log("✅ Login successful");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform logout and verify success
|
||||
*/
|
||||
async logoutAndVerify(): Promise<void> {
|
||||
console.log("🚪 Logging out...");
|
||||
|
||||
// Open profile menu
|
||||
await this.page.getByTestId("profile-popout-menu-trigger").click();
|
||||
|
||||
// Wait for menu to be visible
|
||||
await this.page.getByRole("button", { name: "Log out" }).waitFor({
|
||||
state: "visible",
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
// Click logout
|
||||
await this.page.getByRole("button", { name: "Log out" }).click();
|
||||
|
||||
// Verify we're back on login page
|
||||
await this.page.waitForURL("/login");
|
||||
|
||||
console.log("✅ Logout successful");
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete authentication cycle: login -> logout -> login
|
||||
*/
|
||||
async fullAuthenticationCycle(testUser: TestUser): Promise<void> {
|
||||
console.log("🔄 Starting full authentication cycle...");
|
||||
|
||||
// First login
|
||||
await this.loginAndVerify(testUser);
|
||||
|
||||
// Logout
|
||||
await this.logoutAndVerify();
|
||||
|
||||
// Login again
|
||||
await this.loginAndVerify(testUser);
|
||||
|
||||
console.log("✅ Full authentication cycle completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify user is on marketplace and authenticated
|
||||
*/
|
||||
async verifyAuthenticated(): Promise<void> {
|
||||
await this.page.waitForURL("/marketplace");
|
||||
await this.page.getByTestId("profile-popout-menu-trigger").waitFor({
|
||||
state: "visible",
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify user is on login page (not authenticated)
|
||||
*/
|
||||
async verifyNotAuthenticated(): Promise<void> {
|
||||
await this.page.waitForURL("/login");
|
||||
}
|
||||
}
|
||||
166
autogpt_platform/frontend/src/tests/utils/signup.ts
Normal file
166
autogpt_platform/frontend/src/tests/utils/signup.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { TestUser } from "./auth";
|
||||
|
||||
/**
|
||||
* Create a test user through signup page for test setup
|
||||
* @param page - Playwright page object
|
||||
* @param email - User email (optional, will generate if not provided)
|
||||
* @param password - User password (optional, will generate if not provided)
|
||||
* @param ignoreOnboarding - Skip onboarding and go to marketplace (default: true)
|
||||
* @returns Promise<TestUser> - Created user object
|
||||
*/
|
||||
export async function signupTestUser(
|
||||
page: any,
|
||||
email?: string,
|
||||
password?: string,
|
||||
ignoreOnboarding: boolean = true,
|
||||
): Promise<TestUser> {
|
||||
const userEmail = email || faker.internet.email();
|
||||
const userPassword = password || faker.internet.password({ length: 12 });
|
||||
|
||||
try {
|
||||
// Navigate to signup page
|
||||
await page.goto("http://localhost:3000/signup");
|
||||
|
||||
// Wait for page to load
|
||||
const emailInput = page.getByPlaceholder("m@example.com");
|
||||
await emailInput.waitFor({ state: "visible", timeout: 10000 });
|
||||
|
||||
// Fill form
|
||||
await emailInput.fill(userEmail);
|
||||
const passwordInputs = page.getByTitle("Password");
|
||||
await passwordInputs.nth(0).fill(userPassword);
|
||||
await passwordInputs.nth(1).fill(userPassword);
|
||||
|
||||
// Agree to terms and submit
|
||||
await page.getByRole("checkbox").click();
|
||||
const signupButton = page.getByRole("button", { name: "Sign up" });
|
||||
await signupButton.click();
|
||||
|
||||
// Wait for successful signup - could redirect to onboarding or marketplace
|
||||
|
||||
try {
|
||||
// Wait for either onboarding or marketplace redirect
|
||||
await Promise.race([
|
||||
page.waitForURL(/\/onboarding/, { timeout: 15000 }),
|
||||
page.waitForURL(/\/marketplace/, { timeout: 15000 }),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"❌ Timeout waiting for redirect, current URL:",
|
||||
page.url(),
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const currentUrl = page.url();
|
||||
|
||||
// Handle onboarding or marketplace redirect
|
||||
if (currentUrl.includes("/onboarding") && ignoreOnboarding) {
|
||||
await page.goto("http://localhost:3000/marketplace");
|
||||
await page.waitForLoadState("domcontentloaded", { timeout: 10000 });
|
||||
}
|
||||
|
||||
// Verify we're on the expected final page
|
||||
if (ignoreOnboarding || currentUrl.includes("/marketplace")) {
|
||||
// Verify we're on marketplace
|
||||
await page
|
||||
.getByText(
|
||||
"Bringing you AI agents designed by thinkers from around the world",
|
||||
)
|
||||
.waitFor({ state: "visible", timeout: 10000 });
|
||||
|
||||
// Verify user is authenticated (profile menu visible)
|
||||
await page
|
||||
.getByTestId("profile-popout-menu-trigger")
|
||||
.waitFor({ state: "visible", timeout: 10000 });
|
||||
}
|
||||
|
||||
const testUser: TestUser = {
|
||||
email: userEmail,
|
||||
password: userPassword,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return testUser;
|
||||
} catch (error) {
|
||||
console.error(`❌ Error creating test user ${userEmail}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete signup and navigate to marketplace
|
||||
* @param page - Playwright page object from MCP server
|
||||
* @param email - User email (optional, will generate if not provided)
|
||||
* @param password - User password (optional, will generate if not provided)
|
||||
* @returns Promise<TestUser> - Created user object
|
||||
*/
|
||||
export async function signupAndNavigateToMarketplace(
|
||||
page: any,
|
||||
email?: string,
|
||||
password?: string,
|
||||
): Promise<TestUser> {
|
||||
console.log("🧪 Creating user and navigating to marketplace...");
|
||||
|
||||
// Create the user via signup and automatically navigate to marketplace
|
||||
const testUser = await signupTestUser(page, email, password, true);
|
||||
|
||||
console.log("✅ User successfully created and authenticated in marketplace");
|
||||
return testUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate signup form behavior
|
||||
* @param page - Playwright page object from MCP server
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export async function validateSignupForm(page: any): Promise<void> {
|
||||
console.log("🧪 Validating signup form...");
|
||||
|
||||
await page.goto("http://localhost:3000/signup");
|
||||
|
||||
// Test empty form submission
|
||||
console.log("❌ Testing empty form submission...");
|
||||
const signupButton = page.getByRole("button", { name: "Sign up" });
|
||||
await signupButton.click();
|
||||
|
||||
// Should still be on signup page
|
||||
const currentUrl = page.url();
|
||||
if (currentUrl.includes("/signup")) {
|
||||
console.log("✅ Empty form correctly blocked");
|
||||
} else {
|
||||
console.log("⚠️ Empty form was not blocked as expected");
|
||||
}
|
||||
|
||||
// Test invalid email
|
||||
console.log("❌ Testing invalid email...");
|
||||
await page.getByPlaceholder("m@example.com").fill("invalid-email");
|
||||
await signupButton.click();
|
||||
|
||||
// Should still be on signup page
|
||||
const currentUrl2 = page.url();
|
||||
if (currentUrl2.includes("/signup")) {
|
||||
console.log("✅ Invalid email correctly blocked");
|
||||
} else {
|
||||
console.log("⚠️ Invalid email was not blocked as expected");
|
||||
}
|
||||
|
||||
console.log("✅ Signup form validation completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique test email
|
||||
* @returns string - Unique test email
|
||||
*/
|
||||
export function generateTestEmail(): string {
|
||||
return `test.${Date.now()}.${Math.random().toString(36).substring(7)}@example.com`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secure test password
|
||||
* @returns string - Secure test password
|
||||
*/
|
||||
export function generateTestPassword(): string {
|
||||
return faker.internet.password({ length: 12 });
|
||||
}
|
||||
@@ -1,9 +1,42 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { TestUser } from "./auth";
|
||||
|
||||
export function generateUser() {
|
||||
return {
|
||||
email: faker.internet.email(),
|
||||
password: faker.internet.password(),
|
||||
name: faker.person.fullName(),
|
||||
/**
|
||||
* Generate a test user with random data
|
||||
* @param options - Optional parameters to override defaults
|
||||
* @returns TestUser object with generated data
|
||||
*/
|
||||
export function generateUser(options?: {
|
||||
email?: string;
|
||||
password?: string;
|
||||
name?: string;
|
||||
}): TestUser {
|
||||
console.log("🎲 Generating test user...");
|
||||
|
||||
const user: TestUser = {
|
||||
email: options?.email || faker.internet.email(),
|
||||
password: options?.password || faker.internet.password({ length: 12 }),
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
console.log(`✅ Generated user: ${user.email}`);
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate multiple test users
|
||||
* @param count - Number of users to generate
|
||||
* @returns Array of TestUser objects
|
||||
*/
|
||||
export function generateUsers(count: number): TestUser[] {
|
||||
console.log(`👥 Generating ${count} test users...`);
|
||||
|
||||
const users: TestUser[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
users.push(generateUser());
|
||||
}
|
||||
|
||||
console.log(`✅ Generated ${users.length} test users`);
|
||||
return users;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user