Add E2E tests for AutoGPT Marketplace

The commit adds comprehensive end-to-end tests for the AutoGPT Platform
Marketplace using Playwright. The test suite covers all major
functionality including the main marketplace page, agent details,
creator profiles, search, and filtering.

Key changes include:

- Core marketplace page tests covering listing, search and filtering -
Agent detail page tests for content display and interactions - Creator
profile page tests for profile info and agent listings - Search and
filtering tests with performance benchmarks - Page object models for
maintainable test structure - Full test documentation with configuration
details

The tests ensure proper functionality while maintaining good test
practices and code organization.
This commit is contained in:
abhi1992002
2025-07-01 13:55:33 +05:30
parent d076d0175f
commit 4879b83016
9 changed files with 3468 additions and 0 deletions

View File

@@ -0,0 +1,387 @@
# Marketplace E2E Tests
This directory contains comprehensive End-to-End (E2E) tests for the AutoGPT Platform Marketplace using Playwright.
## Test Overview
The marketplace test suite covers all major functionality of the marketplace including:
- **Main Marketplace Page** (`/marketplace`)
- **Agent Detail Pages** (`/marketplace/agent/{creator}/{agent-name}`)
- **Creator Profile Pages** (`/marketplace/creator/{creator-id}`)
- **Search and Filtering Functionality**
- **Navigation and User Interactions**
## Test Files
### Core Test Files
- **`marketplace.spec.ts`** - Main marketplace page tests
- Page load and structure validation
- Agent and creator displays
- Search functionality
- Category filtering
- Navigation tests
- Performance and accessibility
- **`marketplace-agent.spec.ts`** - Agent detail page tests
- Agent information display
- Download functionality
- Related agents
- Creator navigation
- Content validation
- **`marketplace-creator.spec.ts`** - Creator profile page tests
- Creator information display
- Creator's agents listing
- Profile statistics
- Navigation and interactions
- **`marketplace-search.spec.ts`** - Search and filtering tests
- Search functionality
- Category filtering
- Search + filter combinations
- Performance testing
- Edge cases and error handling
### Page Object Models
- **`pages/marketplace.page.ts`** - Main marketplace page object
- **`pages/agent-detail.page.ts`** - Agent detail page object
- **`pages/creator-profile.page.ts`** - Creator profile page object
### Configuration
- **`marketplace.config.ts`** - Test configuration and helpers
- Timeouts and thresholds
- Test data and selectors
- Helper functions
- Performance metrics
## Running the Tests
### Prerequisites
Make sure you have the development environment running:
```bash
# Start the frontend development server
cd autogpt_platform/frontend
pnpm dev
```
The marketplace tests expect the application to be running on `http://localhost:3000`.
### Run All Marketplace Tests
```bash
# Run all marketplace tests
pnpm test marketplace
# Run with UI (headed mode)
pnpm test-ui marketplace
# Run specific test file
pnpm test marketplace.spec.ts
pnpm test marketplace-agent.spec.ts
pnpm test marketplace-creator.spec.ts
pnpm test marketplace-search.spec.ts
```
### Run Tests by Category
```bash
# Run smoke tests only
pnpm test --grep "@smoke"
# Run performance tests
pnpm test --grep "@performance"
# Run accessibility tests
pnpm test --grep "@accessibility"
# Run search-specific tests
pnpm test --grep "@search"
```
### Debug Mode
```bash
# Run in debug mode with browser visible
pnpm test marketplace.spec.ts --debug
# Run with step-by-step debugging
pnpm test marketplace.spec.ts --ui
```
## Test Structure
### Test Organization
Each test file follows this structure:
```typescript
test.describe("Feature Area", () => {
test.beforeEach(async ({ page }) => {
// Setup code
});
test.describe("Sub-feature", () => {
test("specific functionality", async ({ page }) => {
// Test implementation
});
});
});
```
### Page Object Pattern
Tests use the Page Object Model pattern for maintainability:
```typescript
// Example usage
const marketplacePage = new MarketplacePage(page);
await marketplacePage.searchAgents("Lead");
const agents = await marketplacePage.getAgentCards();
```
## Test Data
### Search Queries
- **Valid queries**: "Lead", "test", "automation", "marketing"
- **Special characters**: "@test", "#hashtag", "test!@#"
- **Edge cases**: Empty string, very long strings, non-existent terms
### Categories
- Marketing
- SEO
- Content Creation
- Automation
- Fun
- Productivity
### Test Agents
Tests work with any agents available in the marketplace, but expect at least:
- Some agents with "Lead" in the name/description
- Multiple creators with multiple agents
- Featured agents and creators
## Key Test Scenarios
### Marketplace Page Tests
1. **Page Load Validation**
- Verify all required sections load
- Check for proper headings and navigation
- Validate agent cards display correctly
2. **Search Functionality**
- Basic text search
- Search with special characters
- Empty and long search queries
- Search result navigation
3. **Category Filtering**
- Click category buttons
- Combine search with filtering
- Multiple category selection
4. **Agent Interactions**
- Click agent cards
- Navigate to agent details
- View featured agents
5. **Creator Interactions**
- Click creator profiles
- Navigate to creator pages
### Agent Detail Tests
1. **Information Display**
- Agent name, creator, description
- Rating and run count
- Categories and version info
- Agent images
2. **Functionality**
- Download button availability
- Creator link navigation
- Related agents display
3. **Navigation**
- Breadcrumb navigation
- Back to marketplace
- Related agent navigation
### Creator Profile Tests
1. **Profile Information**
- Creator name and handle
- Description and statistics
- Top categories
2. **Agent Listings**
- Display creator's agents
- Agent card functionality
- Agent count accuracy
3. **Navigation**
- Agent detail navigation
- Back to marketplace
### Search and Filtering Tests
1. **Search Functionality**
- Real-time search
- Search persistence
- Search result accuracy
2. **Category Filtering**
- Category button responsiveness
- Filter application
- Filter combinations
3. **Performance**
- Search response times
- Filter application speed
- UI responsiveness
## Performance Thresholds
- **Page Load**: < 15 seconds
- **Search Response**: < 5 seconds
- **Category Filtering**: < 5 seconds
- **Navigation**: < 8 seconds
- **Agent Load**: < 8 seconds
## Accessibility Testing
Tests include basic accessibility checks:
- Keyboard navigation
- ARIA attributes
- Proper heading structure
- Button and link accessibility
## Error Handling
Tests verify graceful handling of:
- Non-existent agents/creators
- Network issues
- Empty search results
- Invalid category selection
- Malformed URLs
## Test Configuration
Key configuration in `marketplace.config.ts`:
```typescript
export const MarketplaceTestConfig = {
timeouts: {
pageLoad: 10_000,
navigation: 5_000,
search: 3_000
},
performance: {
maxPageLoadTime: 15_000,
maxSearchTime: 5_000
}
};
```
## Troubleshooting
### Common Issues
1. **Tests timing out**
- Ensure the development server is running
- Check network connectivity
- Increase timeouts if needed
2. **Agent cards not found**
- Verify marketplace has test data
- Check if agent card selectors have changed
- Look for console errors
3. **Search not working**
- Verify search input selector
- Check if search functionality is enabled
- Ensure JavaScript is loaded
4. **Navigation failures**
- Check URL patterns in config
- Verify routing is working
- Look for client-side errors
### Debug Tips
1. **Use headed mode** for visual debugging:
```bash
pnpm test-ui marketplace.spec.ts
```
2. **Add debug logs** in tests:
```typescript
console.log("Current URL:", page.url());
console.log("Agent count:", agents.length);
```
3. **Take screenshots** on failure:
```typescript
await page.screenshot({ path: 'debug-screenshot.png' });
```
4. **Check browser console**:
```typescript
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
```
## Maintenance
### Updating Tests
When marketplace UI changes:
1. Update selectors in `marketplace.config.ts`
2. Modify page object methods
3. Adjust test expectations
4. Update timeouts if needed
### Adding New Tests
1. Follow existing test structure
2. Use page object pattern
3. Add appropriate test tags
4. Include performance and accessibility checks
5. Update this README
## Contributing
When adding new marketplace tests:
1. Use descriptive test names
2. Follow the existing pattern
3. Include both positive and negative test cases
4. Add performance measurements
5. Include accessibility checks
6. Update documentation
## Test Tags
Use these tags to categorize tests:
- `@smoke` - Critical functionality
- `@regression` - Full feature testing
- `@performance` - Performance testing
- `@accessibility` - Accessibility testing
- `@search` - Search functionality
- `@filtering` - Filtering functionality
- `@navigation` - Navigation testing
- `@responsive` - Responsive design testing
Example:
```typescript
test("search functionality works @smoke @search", async ({ page }) => {
// Test implementation
});
```

View File

@@ -0,0 +1,331 @@
import { test } from "./fixtures";
import { MarketplacePage } from "./pages/marketplace.page";
import { AgentDetailPage } from "./pages/agent-detail.page";
import { CreatorProfilePage } from "./pages/creator-profile.page";
test.describe("Marketplace Agent Detail", () => {
let marketplacePage: MarketplacePage;
let agentDetailPage: AgentDetailPage;
let creatorProfilePage: CreatorProfilePage;
test.beforeEach(async ({ page }) => {
marketplacePage = new MarketplacePage(page);
agentDetailPage = new AgentDetailPage(page);
creatorProfilePage = new CreatorProfilePage(page);
// Navigate to marketplace first
await page.goto("/marketplace");
await marketplacePage.waitForPageLoad();
// Navigate to a specific agent detail page
const agents = await marketplacePage.getAgentCards();
if (agents.length > 0) {
await marketplacePage.clickAgentCard(agents[0].name);
await page.waitForTimeout(2000);
await agentDetailPage.waitForPageLoad();
}
});
test.describe("Page Load and Structure", () => {
test("agent detail page loads successfully", async ({ page }) => {
await test.expect(agentDetailPage.isLoaded()).resolves.toBeTruthy();
await test.expect(page.url()).toMatch(/\/marketplace\/agent\/.*\/.*/);
await test
.expect(agentDetailPage.hasCorrectTitle())
.resolves.toBeTruthy();
});
test("has all required agent information", async () => {
await test.expect(agentDetailPage.hasAgentName()).resolves.toBeTruthy();
await test.expect(agentDetailPage.hasCreatorInfo()).resolves.toBeTruthy();
await test.expect(agentDetailPage.hasDescription()).resolves.toBeTruthy();
await test
.expect(agentDetailPage.hasDownloadButton())
.resolves.toBeTruthy();
await test.expect(agentDetailPage.hasRatingInfo()).resolves.toBeTruthy();
await test.expect(agentDetailPage.hasRunsInfo()).resolves.toBeTruthy();
});
test("displays correct agent details", async () => {
const agentDetails = await agentDetailPage.getAgentDetails();
await test.expect(agentDetails.name).toBeTruthy();
await test.expect(agentDetails.creator).toBeTruthy();
await test.expect(agentDetails.description).toBeTruthy();
await test.expect(typeof agentDetails.rating).toBe("number");
await test.expect(typeof agentDetails.runs).toBe("number");
console.log("Agent Details:", agentDetails);
});
test("has breadcrumb navigation", async () => {
await test
.expect(agentDetailPage.hasBreadcrumbNavigation())
.resolves.toBeTruthy();
});
test("displays agent images", async () => {
// Agent may or may not have images, so we check if they exist
const hasImages = await agentDetailPage.hasAgentImages();
console.log("Agent has images:", hasImages);
});
});
test.describe("Agent Information", () => {
test("shows version information", async () => {
const hasVersionInfo = await agentDetailPage.hasVersionInfo();
console.log("Has version info:", hasVersionInfo);
if (hasVersionInfo) {
const agentDetails = await agentDetailPage.getAgentDetails();
await test.expect(agentDetails.version).toBeTruthy();
}
});
test("shows categories if available", async () => {
const hasCategories = await agentDetailPage.hasCategoriesInfo();
console.log("Has categories:", hasCategories);
if (hasCategories) {
const agentDetails = await agentDetailPage.getAgentDetails();
await test.expect(agentDetails.categories.length).toBeGreaterThan(0);
}
});
test("displays rating and runs correctly", async () => {
const agentDetails = await agentDetailPage.getAgentDetails();
// Rating should be between 0 and 5
await test.expect(agentDetails.rating).toBeGreaterThanOrEqual(0);
await test.expect(agentDetails.rating).toBeLessThanOrEqual(5);
// Runs should be non-negative
await test.expect(agentDetails.runs).toBeGreaterThanOrEqual(0);
});
});
test.describe("Agent Interactions", () => {
test("download button is functional", async () => {
await test
.expect(agentDetailPage.hasDownloadButton())
.resolves.toBeTruthy();
// Verify button is clickable (without actually downloading)
const downloadButton = agentDetailPage.downloadButton;
await test.expect(downloadButton.isVisible()).resolves.toBeTruthy();
await test.expect(downloadButton.isEnabled()).resolves.toBeTruthy();
});
test("can navigate to creator profile", async ({ page }) => {
await agentDetailPage.clickCreatorLink();
await page.waitForTimeout(2000);
// Should navigate to creator profile page
await test.expect(page.url()).toMatch(/\/marketplace\/creator\/.*/);
await test.expect(creatorProfilePage.isLoaded()).resolves.toBeTruthy();
});
test("can navigate back to marketplace", async ({ page }) => {
// First ensure we're on agent detail page
await test.expect(page.url()).toMatch(/\/marketplace\/agent\/.*\/.*/);
await agentDetailPage.navigateBackToMarketplace();
await page.waitForTimeout(2000);
// Should be back on marketplace
await test.expect(page.url()).toMatch(/\/marketplace$/);
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
});
test.describe("Related Agents", () => {
test("shows other agents by same creator", async () => {
const hasOtherAgents =
await agentDetailPage.hasOtherAgentsByCreatorSection();
console.log("Has other agents by creator:", hasOtherAgents);
if (hasOtherAgents) {
const relatedAgents = await agentDetailPage.getRelatedAgents();
console.log("Related agents count:", relatedAgents.length);
}
});
test("shows similar agents", async () => {
const hasSimilarAgents = await agentDetailPage.hasSimilarAgentsSection();
console.log("Has similar agents:", hasSimilarAgents);
if (hasSimilarAgents) {
const relatedAgents = await agentDetailPage.getRelatedAgents();
console.log("Similar agents count:", relatedAgents.length);
}
});
test("can click on related agents", async ({ page }) => {
const relatedAgents = await agentDetailPage.getRelatedAgents();
if (relatedAgents.length > 0) {
const firstRelatedAgent = relatedAgents[0];
await agentDetailPage.clickRelatedAgent(firstRelatedAgent.name);
await page.waitForTimeout(2000);
// Should navigate to another agent detail page
await test.expect(page.url()).toMatch(/\/marketplace\/agent\/.*\/.*/);
await test.expect(agentDetailPage.isLoaded()).resolves.toBeTruthy();
}
});
test("related agents have correct information", async () => {
const relatedAgents = await agentDetailPage.getRelatedAgents();
if (relatedAgents.length > 0) {
const firstAgent = relatedAgents[0];
await test.expect(firstAgent.name).toBeTruthy();
await test.expect(firstAgent.creator).toBeTruthy();
await test.expect(typeof firstAgent.rating).toBe("number");
await test.expect(typeof firstAgent.runs).toBe("number");
}
});
});
test.describe("Page Navigation and URL", () => {
test("URL structure is correct", async ({ page }) => {
const url = page.url();
const urlParts = url.split("/");
// URL should be /marketplace/agent/{creator}/{agent-name}
await test.expect(urlParts).toContain("marketplace");
await test.expect(urlParts).toContain("agent");
await test.expect(urlParts.length).toBeGreaterThan(5);
});
test("page title contains agent information", async ({ page }) => {
const title = await page.title();
const agentDetails = await agentDetailPage.getAgentDetails();
// Title should contain agent name or be related to AutoGPT Marketplace
const titleContainsRelevantInfo =
title.includes(agentDetails.name) ||
title.includes("AutoGPT") ||
title.includes("Marketplace") ||
title.includes("Store");
await test.expect(titleContainsRelevantInfo).toBeTruthy();
});
});
test.describe("Content Validation", () => {
test("agent name is displayed prominently", async () => {
const agentName = await agentDetailPage.agentName.textContent();
await test.expect(agentName).toBeTruthy();
await test.expect(agentName?.trim().length).toBeGreaterThan(0);
});
test("creator information is complete", async () => {
const agentDetails = await agentDetailPage.getAgentDetails();
await test.expect(agentDetails.creator).toBeTruthy();
await test.expect(agentDetails.creator.length).toBeGreaterThan(0);
});
test("description provides meaningful information", async () => {
const agentDetails = await agentDetailPage.getAgentDetails();
await test.expect(agentDetails.description).toBeTruthy();
await test.expect(agentDetails.description.length).toBeGreaterThan(10);
});
});
test.describe("Performance and Loading", () => {
test("page loads within reasonable time", async ({ page }, testInfo) => {
const startTime = Date.now();
// Navigate to marketplace and then to an agent
await page.goto("/marketplace");
await marketplacePage.waitForPageLoad();
const agents = await marketplacePage.getAgentCards();
if (agents.length > 0) {
await marketplacePage.clickAgentCard(agents[0].name);
await agentDetailPage.waitForPageLoad();
}
const loadTime = Date.now() - startTime;
// Page should load within 15 seconds
await test.expect(loadTime).toBeLessThan(15000);
testInfo.attach("load-time", {
body: `Agent detail page loaded in ${loadTime}ms`,
});
});
test("images load properly", async () => {
await agentDetailPage.waitForImagesLoad();
const hasImages = await agentDetailPage.hasAgentImages();
if (hasImages) {
const imageCount = await agentDetailPage.agentImages.count();
console.log("Image count:", imageCount);
await test.expect(imageCount).toBeGreaterThan(0);
}
});
test("page metrics are reasonable", async () => {
const metrics = await agentDetailPage.getPageMetrics();
await test.expect(metrics.hasAllRequiredElements).toBeTruthy();
console.log("Agent Detail Page Metrics:", metrics);
});
});
test.describe("Error Handling", () => {
test("handles missing agent gracefully", async ({ page }) => {
// Try to navigate to a non-existent agent
await page.goto("/marketplace/agent/nonexistent/nonexistent-agent");
await page.waitForTimeout(3000);
// Should either show 404 or redirect to marketplace
const url = page.url();
const is404 =
url.includes("404") || (await page.locator("text=404").isVisible());
const redirectedToMarketplace =
url.includes("/marketplace") && !url.includes("/agent/");
await test.expect(is404 || redirectedToMarketplace).toBeTruthy();
});
test("handles network issues gracefully", async ({ page: _ }) => {
// This test would require more advanced setup to simulate network issues
// For now, we just verify the page can handle missing images
const hasImages = await agentDetailPage.hasAgentImages();
console.log("Page handles images:", hasImages);
});
});
test.describe("Accessibility", () => {
test("main content is accessible", async ({ page }) => {
const agentName = page.getByRole("heading").first();
await test.expect(agentName).toBeVisible();
const downloadButton = page.getByRole("button", {
name: "Download agent",
});
await test.expect(downloadButton).toBeVisible();
});
test("navigation elements are accessible", async ({ page }) => {
const creatorLink = page.getByRole("link").first();
await test.expect(creatorLink).toBeVisible();
});
test("keyboard navigation works", async ({ page }) => {
// Test basic keyboard navigation
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
const focusedElement = await page.evaluate(
() => document.activeElement?.tagName,
);
console.log("Focused element after tab navigation:", focusedElement);
});
});
});

View File

@@ -0,0 +1,452 @@
import { test } from "./fixtures";
import { MarketplacePage } from "./pages/marketplace.page";
import { AgentDetailPage } from "./pages/agent-detail.page";
import { CreatorProfilePage } from "./pages/creator-profile.page";
test.describe("Marketplace Creator Profile", () => {
let marketplacePage: MarketplacePage;
let agentDetailPage: AgentDetailPage;
let creatorProfilePage: CreatorProfilePage;
test.beforeEach(async ({ page }) => {
marketplacePage = new MarketplacePage(page);
agentDetailPage = new AgentDetailPage(page);
creatorProfilePage = new CreatorProfilePage(page);
// Navigate to marketplace first
await page.goto("/marketplace");
await marketplacePage.waitForPageLoad();
// Navigate to a creator profile page via featured creators
const creators = await marketplacePage.getFeaturedCreators();
if (creators.length > 0) {
await marketplacePage.clickCreator(creators[0].displayName);
await page.waitForTimeout(2000);
await creatorProfilePage.waitForPageLoad();
}
});
test.describe("Page Load and Structure", () => {
test("creator profile page loads successfully", async ({ page }) => {
await test.expect(creatorProfilePage.isLoaded()).resolves.toBeTruthy();
await test.expect(page.url()).toMatch(/\/marketplace\/creator\/.*/);
await test
.expect(creatorProfilePage.hasCorrectTitle())
.resolves.toBeTruthy();
});
test("has all required creator information", async () => {
await test
.expect(creatorProfilePage.hasCreatorDisplayName())
.resolves.toBeTruthy();
await test
.expect(creatorProfilePage.hasAgentsSection())
.resolves.toBeTruthy();
});
test("displays correct creator profile", async () => {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
await test.expect(creatorProfile.displayName).toBeTruthy();
await test.expect(typeof creatorProfile.agentCount).toBe("number");
await test.expect(creatorProfile.agentCount).toBeGreaterThanOrEqual(0);
console.log("Creator Profile:", creatorProfile);
});
test("has breadcrumb navigation", async () => {
await test
.expect(creatorProfilePage.hasBreadcrumbNavigation())
.resolves.toBeTruthy();
});
});
test.describe("Creator Information", () => {
test("shows creator handle if available", async () => {
const hasHandle = await creatorProfilePage.hasCreatorHandle();
console.log("Has creator handle:", hasHandle);
if (hasHandle) {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
await test.expect(creatorProfile.handle).toBeTruthy();
}
});
test("shows creator avatar if available", async () => {
const hasAvatar = await creatorProfilePage.hasCreatorAvatar();
console.log("Has creator avatar:", hasAvatar);
});
test("shows creator description if available", async () => {
const hasDescription = await creatorProfilePage.hasCreatorDescription();
console.log("Has creator description:", hasDescription);
if (hasDescription) {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
await test.expect(creatorProfile.description).toBeTruthy();
await test.expect(creatorProfile.description.length).toBeGreaterThan(0);
}
});
test("displays statistics if available", async () => {
const hasRating = await creatorProfilePage.hasAverageRatingSection();
const hasRuns = await creatorProfilePage.hasTotalRunsSection();
const hasCategories = await creatorProfilePage.hasTopCategoriesSection();
console.log("Has rating section:", hasRating);
console.log("Has runs section:", hasRuns);
console.log("Has categories section:", hasCategories);
if (hasRating) {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
await test
.expect(creatorProfile.averageRating)
.toBeGreaterThanOrEqual(0);
await test.expect(creatorProfile.averageRating).toBeLessThanOrEqual(5);
}
if (hasRuns) {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
await test.expect(creatorProfile.totalRuns).toBeGreaterThanOrEqual(0);
}
if (hasCategories) {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
console.log("Top categories:", creatorProfile.topCategories);
}
});
});
test.describe("Creator Agents", () => {
test("displays creator's agents", async () => {
await test.expect(creatorProfilePage.hasAgents()).resolves.toBeTruthy();
const agents = await creatorProfilePage.getCreatorAgents();
await test.expect(agents.length).toBeGreaterThan(0);
console.log("Creator agents count:", agents.length);
});
test("agent cards have correct information", async () => {
const agents = await creatorProfilePage.getCreatorAgents();
if (agents.length > 0) {
const firstAgent = agents[0];
await test.expect(firstAgent.name).toBeTruthy();
await test.expect(firstAgent.description).toBeTruthy();
await test.expect(typeof firstAgent.rating).toBe("number");
await test.expect(typeof firstAgent.runs).toBe("number");
console.log("First agent details:", firstAgent);
}
});
test("can click on creator's agents", async ({ page }) => {
const agents = await creatorProfilePage.getCreatorAgents();
if (agents.length > 0) {
const firstAgent = agents[0];
await creatorProfilePage.clickAgent(firstAgent.name);
await page.waitForTimeout(2000);
// Should navigate to agent detail page
await test.expect(page.url()).toMatch(/\/marketplace\/agent\/.*\/.*/);
await test.expect(agentDetailPage.isLoaded()).resolves.toBeTruthy();
}
});
test("agent count matches displayed agents", async () => {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
const agents = await creatorProfilePage.getCreatorAgents();
// The displayed agent count should match or be close to actual agents shown
// (there might be pagination or filtering)
await test
.expect(agents.length)
.toBeLessThanOrEqual(creatorProfile.agentCount + 5);
console.log("Profile agent count:", creatorProfile.agentCount);
console.log("Displayed agents:", agents.length);
});
});
test.describe("Navigation", () => {
test("can navigate back to store", async ({ page }) => {
await creatorProfilePage.navigateBackToStore();
await page.waitForTimeout(2000);
// Should be back on marketplace
await test.expect(page.url()).toMatch(/\/marketplace$/);
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
test("URL structure is correct", async ({ page }) => {
const url = page.url();
const urlParts = url.split("/");
// URL should be /marketplace/creator/{creator-handle}
await test.expect(urlParts).toContain("marketplace");
await test.expect(urlParts).toContain("creator");
await test.expect(urlParts.length).toBeGreaterThan(4);
});
test("page title contains creator information", async ({ page }) => {
const title = await page.title();
const creatorProfile = await creatorProfilePage.getCreatorProfile();
// Title should contain creator name or be related to AutoGPT Store
const titleContainsRelevantInfo =
title.includes(creatorProfile.displayName) ||
title.includes(creatorProfile.username) ||
title.includes("AutoGPT") ||
title.includes("Store") ||
title.includes("Marketplace");
await test.expect(titleContainsRelevantInfo).toBeTruthy();
});
});
test.describe("Content Validation", () => {
test("creator name is displayed prominently", async () => {
const creatorName =
await creatorProfilePage.creatorDisplayName.textContent();
await test.expect(creatorName).toBeTruthy();
await test.expect(creatorName?.trim().length).toBeGreaterThan(0);
});
test("agents section has meaningful heading", async ({ page }) => {
const agentsHeading = page.getByRole("heading", { name: /Agents by/i });
await test.expect(agentsHeading).toBeVisible();
});
test("creator information is comprehensive", async () => {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
// Creator should have at least a display name and username
await test.expect(creatorProfile.displayName).toBeTruthy();
await test.expect(creatorProfile.username).toBeTruthy();
});
});
test.describe("Agent Filtering and Search", () => {
test("can search creator's agents", async () => {
const agents = await creatorProfilePage.getCreatorAgents();
if (agents.length > 0) {
const searchQuery = agents[0].name.substring(0, 3);
const filteredAgents =
await creatorProfilePage.searchCreatorAgents(searchQuery);
console.log("Search query:", searchQuery);
console.log("Filtered agents:", filteredAgents.length);
// Filtered results should be subset of all agents
await test
.expect(filteredAgents.length)
.toBeLessThanOrEqual(agents.length);
}
});
test("agents can be grouped by categories if available", async () => {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
if (creatorProfile.topCategories.length > 0) {
const firstCategory = creatorProfile.topCategories[0];
const categoryAgents =
await creatorProfilePage.getAgentsByCategory(firstCategory);
console.log("Category:", firstCategory);
console.log("Category agents:", categoryAgents.length);
}
});
});
test.describe("Performance and Loading", () => {
test("page loads within reasonable time", async ({ page }, testInfo) => {
const startTime = Date.now();
// Navigate to marketplace and then to a creator
await page.goto("/marketplace");
await marketplacePage.waitForPageLoad();
const creators = await marketplacePage.getFeaturedCreators();
if (creators.length > 0) {
await marketplacePage.clickCreator(creators[0].displayName);
await creatorProfilePage.waitForPageLoad();
}
const loadTime = Date.now() - startTime;
// Page should load within 15 seconds
await test.expect(loadTime).toBeLessThan(15000);
testInfo.attach("load-time", {
body: `Creator profile page loaded in ${loadTime}ms`,
});
});
test("agents load properly", async () => {
await creatorProfilePage.waitForAgentsLoad();
const hasAgents = await creatorProfilePage.hasAgents();
if (hasAgents) {
const agents = await creatorProfilePage.getCreatorAgents();
console.log("Loaded agents count:", agents.length);
await test.expect(agents.length).toBeGreaterThan(0);
}
});
test("page metrics are reasonable", async () => {
const metrics = await creatorProfilePage.getPageMetrics();
await test.expect(metrics.hasAllRequiredElements).toBeTruthy();
await test.expect(metrics.agentCount).toBeGreaterThanOrEqual(0);
console.log("Creator Profile Page Metrics:", metrics);
});
});
test.describe("Responsive Design", () => {
test("page works on mobile viewport", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.reload();
await creatorProfilePage.waitForPageLoad();
await test.expect(creatorProfilePage.isLoaded()).resolves.toBeTruthy();
await test
.expect(creatorProfilePage.hasCreatorDisplayName())
.resolves.toBeTruthy();
});
test("page works on tablet viewport", async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.reload();
await creatorProfilePage.waitForPageLoad();
await test.expect(creatorProfilePage.isLoaded()).resolves.toBeTruthy();
await test
.expect(creatorProfilePage.hasAgentsSection())
.resolves.toBeTruthy();
});
test("scrolling works correctly", async () => {
await creatorProfilePage.scrollToAgentsSection();
await test
.expect(creatorProfilePage.hasAgentsSection())
.resolves.toBeTruthy();
});
});
test.describe("Error Handling", () => {
test("handles missing creator gracefully", async ({ page }) => {
// Try to navigate to a non-existent creator
await page.goto("/marketplace/creator/nonexistent-creator");
await page.waitForTimeout(3000);
// Should either show 404 or redirect to marketplace
const url = page.url();
const is404 =
url.includes("404") || (await page.locator("text=404").isVisible());
const redirectedToMarketplace =
url.includes("/marketplace") && !url.includes("/creator/");
await test.expect(is404 || redirectedToMarketplace).toBeTruthy();
});
test("handles creator with no agents gracefully", async ({ page: _ }) => {
// This test would be relevant for creators with 0 agents
const hasAgents = await creatorProfilePage.hasAgents();
if (!hasAgents) {
// Should still show the creator profile information
await test
.expect(creatorProfilePage.hasCreatorDisplayName())
.resolves.toBeTruthy();
await test
.expect(creatorProfilePage.hasAgentsSection())
.resolves.toBeTruthy();
}
});
});
test.describe("Accessibility", () => {
test("main content is accessible", async ({ page }) => {
const creatorName = page.getByRole("heading").first();
await test.expect(creatorName).toBeVisible();
const agentsSection = page.getByRole("heading", { name: /Agents by/i });
await test.expect(agentsSection).toBeVisible();
});
test("navigation elements are accessible", async ({ page }) => {
const storeLink = page.getByRole("link", { name: "Store" });
await test.expect(storeLink).toBeVisible();
});
test("agent cards are accessible", async ({ page }) => {
const agentButtons = page
.getByRole("button")
.filter({ hasText: /agent card/i });
const agentCount = await agentButtons.count();
if (agentCount > 0) {
await test.expect(agentButtons.first()).toBeVisible();
}
});
test("keyboard navigation works", async ({ page }) => {
// Test basic keyboard navigation
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
const focusedElement = await page.evaluate(
() => document.activeElement?.tagName,
);
console.log("Focused element after tab navigation:", focusedElement);
});
});
test.describe("Data Consistency", () => {
test("creator information is consistent across pages", async ({ page }) => {
// Get creator info from profile page
const creatorProfile = await creatorProfilePage.getCreatorProfile();
// Navigate to one of their agents
const agents = await creatorProfilePage.getCreatorAgents();
if (agents.length > 0) {
await creatorProfilePage.clickAgent(agents[0].name);
await page.waitForTimeout(2000);
// Check that the creator name matches on agent detail page
const agentDetails = await agentDetailPage.getAgentDetails();
// Creator names should match (allowing for different formats)
const creatorNamesMatch =
agentDetails.creator
.toLowerCase()
.includes(creatorProfile.displayName.toLowerCase()) ||
agentDetails.creator
.toLowerCase()
.includes(creatorProfile.username.toLowerCase()) ||
creatorProfile.displayName
.toLowerCase()
.includes(agentDetails.creator.toLowerCase());
await test.expect(creatorNamesMatch).toBeTruthy();
}
});
test("agent count is reasonable", async () => {
const creatorProfile = await creatorProfilePage.getCreatorProfile();
const displayedAgents = await creatorProfilePage.getCreatorAgents();
// Agent count should be reasonable (not negative, not impossibly high)
await test.expect(creatorProfile.agentCount).toBeGreaterThanOrEqual(0);
await test.expect(creatorProfile.agentCount).toBeLessThan(1000); // Reasonable upper limit
// Displayed agents should not exceed claimed agent count significantly
await test
.expect(displayedAgents.length)
.toBeLessThanOrEqual(creatorProfile.agentCount + 10);
});
});
});

View File

@@ -0,0 +1,510 @@
import { test } from "./fixtures";
import { MarketplacePage } from "./pages/marketplace.page";
import { AgentDetailPage } from "./pages/agent-detail.page";
test.describe("Marketplace Search and Filtering", () => {
let marketplacePage: MarketplacePage;
let agentDetailPage: AgentDetailPage;
test.beforeEach(async ({ page }) => {
marketplacePage = new MarketplacePage(page);
agentDetailPage = new AgentDetailPage(page);
// Navigate to marketplace
await page.goto("/marketplace");
await marketplacePage.waitForPageLoad();
});
test.describe("Search Functionality", () => {
test("search input is visible and accessible", async () => {
await test.expect(marketplacePage.hasSearchInput()).resolves.toBeTruthy();
const searchInput = marketplacePage.searchInput;
await test.expect(searchInput.isVisible()).resolves.toBeTruthy();
await test.expect(searchInput.isEnabled()).resolves.toBeTruthy();
// Check placeholder text
const placeholder = await searchInput.getAttribute("placeholder");
await test.expect(placeholder).toBeTruthy();
console.log("Search placeholder:", placeholder);
});
test("can perform basic search", async ({ page }) => {
const searchQuery = "Lead";
await marketplacePage.searchAgents(searchQuery);
// Verify search was executed
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe(searchQuery);
// Wait for potential search results to load
await page.waitForTimeout(2000);
// Verify page is still functional after search
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
test("can search with different queries", async ({ page }) => {
const searchQueries = ["Lead", "test", "automation", "marketing"];
for (const query of searchQueries) {
await marketplacePage.searchAgents(query);
await page.waitForTimeout(1000);
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe(query);
// Clear search for next iteration
await marketplacePage.clearSearch();
await page.waitForTimeout(500);
}
});
test("can clear search", async () => {
// Perform a search first
await marketplacePage.searchAgents("test query");
let searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("test query");
// Clear the search
await marketplacePage.clearSearch();
searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("");
});
test("search with empty query", async ({ page }) => {
await marketplacePage.searchAgents("");
await page.waitForTimeout(1000);
// Page should remain functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
const agents = await marketplacePage.getAgentCards();
await test.expect(agents.length).toBeGreaterThanOrEqual(0);
});
test("search with special characters", async ({ page }) => {
const specialQueries = ["@test", "#hashtag", "test!@#", "test-agent"];
for (const query of specialQueries) {
await marketplacePage.searchAgents(query);
await page.waitForTimeout(1000);
// Page should handle special characters gracefully
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
await marketplacePage.clearSearch();
}
});
test("search with very long query", async ({ page }) => {
const longQuery = "a".repeat(200);
await marketplacePage.searchAgents(longQuery);
await page.waitForTimeout(1000);
// Page should handle long queries gracefully
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
test("search preserves functionality", async ({ page }) => {
await marketplacePage.searchAgents("Lead");
await page.waitForTimeout(2000);
// After search, core functionality should still work
await test
.expect(marketplacePage.hasFeaturedAgentsSection())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasTopAgentsSection())
.resolves.toBeTruthy();
// Should still be able to interact with agents
const agents = await marketplacePage.getAgentCards();
if (agents.length > 0) {
// Verify agent cards are still clickable
await test
.expect(marketplacePage.agentCards.first().isVisible())
.resolves.toBeTruthy();
}
});
});
test.describe("Category Filtering", () => {
test("displays available categories", async () => {
const categories = await marketplacePage.getAvailableCategories();
await test.expect(categories.length).toBeGreaterThan(0);
console.log("Available categories:", categories);
// Check for expected common categories
const categoryText = categories.join(" ").toLowerCase();
const hasExpectedCategories =
categoryText.includes("marketing") ||
categoryText.includes("automation") ||
categoryText.includes("content") ||
categoryText.includes("seo") ||
categoryText.includes("fun") ||
categoryText.includes("productivity");
await test.expect(hasExpectedCategories).toBeTruthy();
});
test("category buttons are clickable", async ({ page }) => {
const categories = await marketplacePage.getAvailableCategories();
if (categories.length > 0) {
const firstCategory = categories[0];
console.log("Testing category:", firstCategory);
await marketplacePage.clickCategory(firstCategory);
await page.waitForTimeout(2000);
// Page should remain functional after category click
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
}
});
test("can click multiple categories", async ({ page }) => {
const categories = await marketplacePage.getAvailableCategories();
// Test clicking up to 3 categories
const categoriesToTest = categories.slice(
0,
Math.min(3, categories.length),
);
for (const category of categoriesToTest) {
await marketplacePage.clickCategory(category);
await page.waitForTimeout(1000);
console.log("Clicked category:", category);
// Verify page remains functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
}
});
test("category filtering preserves page structure", async ({ page }) => {
const categories = await marketplacePage.getAvailableCategories();
if (categories.length > 0) {
await marketplacePage.clickCategory(categories[0]);
await page.waitForTimeout(2000);
// Core page structure should remain
await test
.expect(marketplacePage.hasMainHeading())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasSearchInput())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasCategoryButtons())
.resolves.toBeTruthy();
}
});
});
test.describe("Search and Filter Combination", () => {
test("can combine search with category filtering", async ({ page }) => {
// First perform a search
await marketplacePage.searchAgents("Lead");
await page.waitForTimeout(1000);
// Then click a category
const categories = await marketplacePage.getAvailableCategories();
if (categories.length > 0) {
await marketplacePage.clickCategory(categories[0]);
await page.waitForTimeout(2000);
// Both search and category should be applied
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("Lead");
// Page should remain functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
}
});
test("can modify search after category selection", async ({ page }) => {
const categories = await marketplacePage.getAvailableCategories();
if (categories.length > 0) {
// First select a category
await marketplacePage.clickCategory(categories[0]);
await page.waitForTimeout(1000);
// Then perform a search
await marketplacePage.searchAgents("automation");
await page.waitForTimeout(2000);
// Search should be applied
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("automation");
// Page should remain functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
}
});
test("can clear search while maintaining category filter", async ({
page,
}) => {
const categories = await marketplacePage.getAvailableCategories();
if (categories.length > 0) {
// Apply category and search
await marketplacePage.clickCategory(categories[0]);
await page.waitForTimeout(1000);
await marketplacePage.searchAgents("test");
await page.waitForTimeout(1000);
// Clear search
await marketplacePage.clearSearch();
// Search should be cleared but page should remain functional
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("");
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
}
});
});
test.describe("Search Results and Navigation", () => {
test("can navigate from search results to agent detail", async ({
page,
}) => {
await marketplacePage.searchAgents("Lead");
await page.waitForTimeout(2000);
// Try to find and click an agent from results
const agents = await marketplacePage.getAgentCards();
if (agents.length > 0) {
const firstAgent = agents[0];
await marketplacePage.clickAgentCard(firstAgent.name);
await page.waitForTimeout(2000);
// Should navigate to agent detail page
await test.expect(page.url()).toMatch(/\/marketplace\/agent\/.*\/.*/);
await test.expect(agentDetailPage.isLoaded()).resolves.toBeTruthy();
}
});
test("can return to search results after viewing agent", async ({
page,
}) => {
await marketplacePage.searchAgents("Lead");
await page.waitForTimeout(2000);
const agents = await marketplacePage.getAgentCards();
if (agents.length > 0) {
// Go to agent detail
await marketplacePage.clickAgentCard(agents[0].name);
await page.waitForTimeout(2000);
// Navigate back to marketplace
await agentDetailPage.navigateBackToMarketplace();
await page.waitForTimeout(2000);
// Should return to marketplace with search preserved
await test.expect(page.url()).toMatch(/\/marketplace/);
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
}
});
test("search results maintain agent card functionality", async ({
page,
}) => {
await marketplacePage.searchAgents("test");
await page.waitForTimeout(2000);
const agents = await marketplacePage.getAgentCards();
if (agents.length > 0) {
const firstAgent = agents[0];
// Agent cards should have all required information
await test.expect(firstAgent.name).toBeTruthy();
await test.expect(firstAgent.creator).toBeTruthy();
await test.expect(typeof firstAgent.runs).toBe("number");
await test.expect(typeof firstAgent.rating).toBe("number");
}
});
});
test.describe("Performance and User Experience", () => {
test("search response time is reasonable", async ({ page }, testInfo) => {
const startTime = Date.now();
await marketplacePage.searchAgents("Lead Finder");
await page.waitForTimeout(2000);
const searchTime = Date.now() - startTime;
// Search should complete within 5 seconds
await test.expect(searchTime).toBeLessThan(5000);
testInfo.attach("search-time", {
body: `Search completed in ${searchTime}ms`,
});
});
test("category filtering response time is reasonable", async ({
page,
}, testInfo) => {
const categories = await marketplacePage.getAvailableCategories();
if (categories.length > 0) {
const startTime = Date.now();
await marketplacePage.clickCategory(categories[0]);
await page.waitForTimeout(2000);
const filterTime = Date.now() - startTime;
// Category filtering should complete within 5 seconds
await test.expect(filterTime).toBeLessThan(5000);
testInfo.attach("filter-time", {
body: `Category filtering completed in ${filterTime}ms`,
});
}
});
test("search input provides immediate feedback", async ({ page }) => {
// Type character by character to test responsiveness
const query = "Lead";
for (let i = 0; i < query.length; i++) {
await marketplacePage.searchInput.fill(query.substring(0, i + 1));
await page.waitForTimeout(100);
const currentValue = await marketplacePage.searchInput.inputValue();
await test.expect(currentValue).toBe(query.substring(0, i + 1));
}
});
test("UI remains responsive during search", async ({ page }) => {
await marketplacePage.searchAgents("automation");
// During search, UI should remain interactive
await test
.expect(marketplacePage.searchInput.isEnabled())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasCategoryButtons())
.resolves.toBeTruthy();
await page.waitForTimeout(2000);
// After search, everything should still be functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
});
test.describe("Edge Cases and Error Handling", () => {
test("handles search with no results gracefully", async ({ page }) => {
await marketplacePage.searchAgents("zyxwvutsrqponmlkjihgfedcba");
await page.waitForTimeout(3000);
// Page should remain functional even with no results
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
await test.expect(marketplacePage.hasSearchInput()).resolves.toBeTruthy();
});
test("handles rapid search queries", async ({ page }) => {
const queries = ["a", "ab", "abc", "abcd", "abcde"];
for (const query of queries) {
await marketplacePage.searchAgents(query);
await page.waitForTimeout(200);
}
// Page should handle rapid changes gracefully
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
const finalValue = await marketplacePage.searchInput.inputValue();
await test.expect(finalValue).toBe("abcde");
});
test("handles clicking non-existent category", async ({ page }) => {
try {
await marketplacePage.clickCategory("NonExistentCategory123");
await page.waitForTimeout(1000);
// Page should remain functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
} catch (_error) {
// This is expected for non-existent categories
console.log("Expected error for non-existent category");
}
});
test("search preserves state across page interactions", async ({
page,
}) => {
await marketplacePage.searchAgents("Lead");
await page.waitForTimeout(1000);
// Scroll the page
await marketplacePage.scrollToSection("Featured Creators");
await page.waitForTimeout(1000);
// Search should still be preserved
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("Lead");
// Page should remain functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
});
test.describe("Accessibility in Search and Filtering", () => {
test("search input is keyboard accessible", async ({ page }) => {
// Navigate to search input using keyboard
await page.keyboard.press("Tab");
// Type in search
await page.keyboard.type("Lead");
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("Lead");
});
test("category buttons are keyboard accessible", async ({ page }) => {
// Use keyboard to navigate to categories
for (let i = 0; i < 10; i++) {
await page.keyboard.press("Tab");
const focusedElement = await page.evaluate(
() => document.activeElement?.textContent,
);
if (
focusedElement &&
focusedElement.toLowerCase().includes("marketing")
) {
await page.keyboard.press("Enter");
await page.waitForTimeout(1000);
break;
}
}
// Page should remain functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
test("search has proper ARIA attributes", async () => {
const searchInput = marketplacePage.searchInput;
// Check for accessibility attributes
const placeholder = await searchInput.getAttribute("placeholder");
const role = await searchInput.getAttribute("role");
const ariaLabel = await searchInput.getAttribute("aria-label");
// At least one accessibility attribute should be present
const hasAccessibilityAttribute = placeholder || role || ariaLabel;
await test.expect(hasAccessibilityAttribute).toBeTruthy();
});
});
});

View File

@@ -0,0 +1,347 @@
// marketplace.config.ts
export const MarketplaceTestConfig = {
// Test timeouts
timeouts: {
pageLoad: 10_000,
navigation: 5_000,
search: 3_000,
agentLoad: 8_000,
imageLoad: 10_000,
},
// Expected page elements
expectedElements: {
marketplace: {
mainHeading: "Explore AI agents built for you by the community",
sections: [
"Featured agents",
"Top Agents",
"Featured Creators",
"Become a Creator",
],
categories: ["Marketing", "SEO", "Content Creation", "Automation", "Fun"],
},
agentDetail: {
requiredElements: [
"agent-name",
"creator-info",
"description",
"download-button",
"rating-info",
"runs-info",
],
optionalElements: [
"version-info",
"categories",
"agent-images",
"breadcrumb-navigation",
],
},
creatorProfile: {
requiredElements: ["creator-name", "agents-section"],
optionalElements: [
"creator-handle",
"creator-avatar",
"description",
"statistics",
"top-categories",
],
},
},
// Test data
testData: {
searchQueries: {
valid: ["Lead", "test", "automation", "marketing", "content"],
special: ["@test", "#hashtag", "test!@#", "test-agent"],
edge: ["", "a".repeat(200), "zyxwvutsrqponmlkjihgfedcba"],
},
categories: {
common: [
"marketing",
"automation",
"content",
"seo",
"fun",
"productivity",
],
fallback: "Marketing", // Default category to test if others fail
},
},
// URL patterns
urlPatterns: {
marketplace: /\/marketplace$/,
agent: /\/marketplace\/agent\/.*\/.*/,
creator: /\/marketplace\/creator\/.*/,
},
// Performance thresholds
performance: {
maxPageLoadTime: 15_000,
maxSearchTime: 5_000,
maxFilterTime: 5_000,
maxNavigationTime: 8_000,
},
// Viewport configurations for responsive testing
viewports: {
mobile: { width: 375, height: 667 },
tablet: { width: 768, height: 1024 },
desktop: { width: 1920, height: 1080 },
},
// Test selectors
selectors: {
marketplace: {
searchInput: '[data-testid="store-search-input"]',
agentCards: 'button[data-testid*="agent-card"]',
categoryButtons: '[data-testid*="category-"]',
featuredAgents: 'h2:has-text("Featured agents") + *',
topAgents: 'h2:has-text("Top Agents") + *',
featuredCreators: 'h2:has-text("Featured Creators") + *',
becomeCreator: 'button:has-text("Become a Creator")',
},
agentDetail: {
agentName: "h1, h2, h3",
creatorLink: 'a[href*="/marketplace/creator/"]',
downloadButton: 'button:has-text("Download agent")',
relatedAgents: 'button[data-testid*="agent-card"]',
breadcrumb: 'nav, div:has-text("Marketplace")',
},
creatorProfile: {
displayName: "h1",
handle: 'div:has-text("@")',
agentsSection: 'h2:has-text("Agents by") + *',
agentCards: 'button[data-testid*="agent-card"]',
},
},
};
// Helper functions for marketplace tests
export const MarketplaceTestHelpers = {
// Wait for element with retry
async waitForElementWithRetry(
page: any,
selector: string,
maxRetries: number = 3,
timeout: number = 5000,
): Promise<boolean> {
for (let i = 0; i < maxRetries; i++) {
try {
await page.waitForSelector(selector, { timeout });
return true;
} catch (_error) {
if (i === maxRetries - 1) {
console.error(
`Failed to find element ${selector} after ${maxRetries} retries`,
);
return false;
}
await page.waitForTimeout(1000);
}
}
return false;
},
// Extract agent data from card element
async extractAgentFromCard(card: any): Promise<any> {
try {
const nameElement = await card.locator("h3").first();
const name = (await nameElement.textContent())?.trim() || "";
const creatorElement = await card.locator('p:has-text("by ")').first();
const creatorText = await creatorElement.textContent();
const creator = creatorText?.replace("by ", "").trim() || "";
const descriptionElement = await card.locator("p").nth(1);
const description =
(await descriptionElement.textContent())?.trim() || "";
const runsElement = await card.locator('div:has-text("runs")');
const runsText = await runsElement.textContent();
const runs = parseInt(runsText?.match(/\d+/)?.[0] || "0");
const ratingElement = await card.locator('div:has-text(".")').first();
const ratingText = await ratingElement.textContent();
const rating = parseFloat(ratingText?.match(/\d+\.\d+/)?.[0] || "0");
return {
name,
creator,
description,
runs,
rating,
isValid: name.length > 0 && creator.length > 0,
};
} catch (error) {
console.error("Error extracting agent data:", error);
return {
name: "",
creator: "",
description: "",
runs: 0,
rating: 0,
isValid: false,
};
}
},
// Validate URL structure
validateUrl(
url: string,
expectedType: "marketplace" | "agent" | "creator",
): boolean {
const patterns = MarketplaceTestConfig.urlPatterns;
switch (expectedType) {
case "marketplace":
return patterns.marketplace.test(url);
case "agent":
return patterns.agent.test(url);
case "creator":
return patterns.creator.test(url);
default:
return false;
}
},
// Generate test metrics
generateTestMetrics(
startTime: number,
elementCounts: { [key: string]: number },
errors: string[] = [],
) {
const endTime = Date.now();
const duration = endTime - startTime;
return {
duration,
timestamp: new Date().toISOString(),
elementCounts,
errors,
performance: {
isWithinThreshold:
duration < MarketplaceTestConfig.performance.maxPageLoadTime,
loadTime: duration,
},
};
},
// Common assertions for marketplace tests
async assertMarketplacePageStructure(page: any): Promise<void> {
const config = MarketplaceTestConfig.expectedElements.marketplace;
// Check main heading
const mainHeading = page.getByRole("heading", { name: config.mainHeading });
if (!(await mainHeading.isVisible())) {
throw new Error("Main heading not visible");
}
// Check required sections
for (const section of config.sections) {
const sectionElement = page.getByRole("heading", { name: section });
if (!(await sectionElement.isVisible())) {
console.warn(`Section "${section}" not visible`);
}
}
// Check search input
const searchInput = page.getByTestId("store-search-input");
if (!(await searchInput.isVisible())) {
throw new Error("Search input not visible");
}
},
// Wait for agents to load with retry
async waitForAgentsLoad(page: any, minCount: number = 1): Promise<boolean> {
const maxRetries = 5;
const retryDelay = 2000;
for (let i = 0; i < maxRetries; i++) {
try {
await page.waitForSelector('button[data-testid*="agent-card"]', {
timeout: 5000,
});
const agentCount = await page
.locator('button[data-testid*="agent-card"]')
.count();
if (agentCount >= minCount) {
return true;
}
} catch (_error) {
console.log(`Attempt ${i + 1}: Waiting for agents to load...`);
}
if (i < maxRetries - 1) {
await page.waitForTimeout(retryDelay);
}
}
console.warn("Agents did not load within expected time");
return false;
},
// Clean up search state
async cleanupSearchState(page: any): Promise<void> {
try {
const searchInput = page.getByTestId("store-search-input");
await searchInput.clear();
await page.waitForTimeout(500);
} catch (error) {
console.warn("Could not clear search input:", error);
}
},
// Validate agent card data
validateAgentData(agent: any): boolean {
return (
typeof agent.name === "string" &&
agent.name.length > 0 &&
typeof agent.creator === "string" &&
agent.creator.length > 0 &&
typeof agent.runs === "number" &&
agent.runs >= 0 &&
typeof agent.rating === "number" &&
agent.rating >= 0 &&
agent.rating <= 5
);
},
// Common test data for reuse
getTestSearchQueries(): string[] {
return MarketplaceTestConfig.testData.searchQueries.valid;
},
getTestCategories(): string[] {
return MarketplaceTestConfig.testData.categories.common;
},
// Performance measurement helper
async measurePerformance<T>(
operation: () => Promise<T>,
operationName: string,
): Promise<{ result: T; duration: number; performanceLog: string }> {
const startTime = Date.now();
const result = await operation();
const duration = Date.now() - startTime;
const performanceLog = `${operationName} completed in ${duration}ms`;
console.log(performanceLog);
return { result, duration, performanceLog };
},
};
// Export test tags for organization
export const MarketplaceTestTags = {
SMOKE: "@smoke",
REGRESSION: "@regression",
PERFORMANCE: "@performance",
ACCESSIBILITY: "@accessibility",
SEARCH: "@search",
FILTERING: "@filtering",
NAVIGATION: "@navigation",
RESPONSIVE: "@responsive",
};

View File

@@ -0,0 +1,331 @@
import { test } from "./fixtures";
import { MarketplacePage } from "./pages/marketplace.page";
import { AgentDetailPage } from "./pages/agent-detail.page";
import { CreatorProfilePage } from "./pages/creator-profile.page";
test.describe("Marketplace", () => {
let marketplacePage: MarketplacePage;
let agentDetailPage: AgentDetailPage;
let creatorProfilePage: CreatorProfilePage;
test.beforeEach(async ({ page }) => {
marketplacePage = new MarketplacePage(page);
agentDetailPage = new AgentDetailPage(page);
creatorProfilePage = new CreatorProfilePage(page);
// Navigate to marketplace
await page.goto("/marketplace");
await marketplacePage.waitForPageLoad();
});
test.describe("Page Load and Structure", () => {
test("marketplace page loads successfully", async ({ page }) => {
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
await test.expect(page).toHaveURL(/.*\/marketplace/);
await test
.expect(marketplacePage.hasCorrectTitle())
.resolves.toBeTruthy();
});
test("has all required sections", async () => {
await test.expect(marketplacePage.hasMainHeading()).resolves.toBeTruthy();
await test.expect(marketplacePage.hasSearchInput()).resolves.toBeTruthy();
await test
.expect(marketplacePage.hasCategoryButtons())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasFeaturedAgentsSection())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasTopAgentsSection())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasFeaturedCreatorsSection())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasBecomeCreatorSection())
.resolves.toBeTruthy();
});
test("displays agent cards with correct information", async () => {
await marketplacePage.waitForAgentsToLoad();
const agents = await marketplacePage.getAgentCards();
await test.expect(agents.length).toBeGreaterThan(0);
if (agents.length > 0) {
const firstAgent = agents[0];
await test.expect(firstAgent.name).toBeTruthy();
await test.expect(firstAgent.creator).toBeTruthy();
await test.expect(typeof firstAgent.runs).toBe("number");
await test.expect(typeof firstAgent.rating).toBe("number");
}
});
test("displays featured creators", async () => {
const creators = await marketplacePage.getFeaturedCreators();
await test.expect(creators.length).toBeGreaterThan(0);
if (creators.length > 0) {
const firstCreator = creators[0];
await test.expect(firstCreator.username).toBeTruthy();
await test.expect(firstCreator.displayName).toBeTruthy();
await test.expect(typeof firstCreator.agentCount).toBe("number");
}
});
});
test.describe("Search Functionality", () => {
test("search input is visible and functional", async ({ page }) => {
await test.expect(marketplacePage.hasSearchInput()).resolves.toBeTruthy();
await marketplacePage.searchAgents("test");
await page.waitForTimeout(1000);
// Verify search was performed (URL or content change)
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("test");
});
test("can search for specific agents", async ({ page }) => {
await marketplacePage.searchAgents("Lead");
await page.waitForTimeout(2000);
// Verify search results or that search was executed
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("Lead");
});
test("can clear search", async () => {
await marketplacePage.searchAgents("test query");
await marketplacePage.clearSearch();
const searchValue = await marketplacePage.searchInput.inputValue();
await test.expect(searchValue).toBe("");
});
});
test.describe("Category Filtering", () => {
test("displays category buttons", async () => {
const categories = await marketplacePage.getAvailableCategories();
await test.expect(categories.length).toBeGreaterThan(0);
// Check for common categories
const categoryText = categories.join(" ").toLowerCase();
const hasCommonCategories =
categoryText.includes("marketing") ||
categoryText.includes("automation") ||
categoryText.includes("content") ||
categoryText.includes("seo") ||
categoryText.includes("fun");
await test.expect(hasCommonCategories).toBeTruthy();
});
test("can click category buttons", async ({ page }) => {
const categories = await marketplacePage.getAvailableCategories();
if (categories.length > 0) {
const firstCategory = categories[0];
await marketplacePage.clickCategory(firstCategory);
await page.waitForTimeout(1000);
// Verify category was clicked (could check for URL change or filter application)
// This is a basic interaction test
}
});
});
test.describe("Agent Interactions", () => {
test("can click featured agent and navigate to detail page", async ({
page,
}) => {
const featuredAgents = await marketplacePage.getFeaturedAgents();
if (featuredAgents.length > 0) {
const firstAgent = featuredAgents[0];
await marketplacePage.clickFeaturedAgent(firstAgent.name);
// Wait for navigation
await page.waitForTimeout(2000);
// Verify we're on an agent detail page
await test.expect(page.url()).toMatch(/\/marketplace\/agent\/.*\/.*/);
await test.expect(agentDetailPage.isLoaded()).resolves.toBeTruthy();
}
});
test("can click agent card and navigate to detail page", async ({
page,
}) => {
await marketplacePage.waitForAgentsToLoad();
const agents = await marketplacePage.getAgentCards();
if (agents.length > 0) {
const firstAgent = agents[0];
await marketplacePage.clickAgentCard(firstAgent.name);
// Wait for navigation
await page.waitForTimeout(2000);
// Verify we're on an agent detail page
await test.expect(page.url()).toMatch(/\/marketplace\/agent\/.*\/.*/);
await test.expect(agentDetailPage.isLoaded()).resolves.toBeTruthy();
}
});
});
test.describe("Creator Interactions", () => {
test("can click creator and navigate to profile page", async ({ page }) => {
const creators = await marketplacePage.getFeaturedCreators();
if (creators.length > 0) {
const firstCreator = creators[0];
await marketplacePage.clickCreator(firstCreator.displayName);
// Wait for navigation
await page.waitForTimeout(2000);
// Verify we're on a creator profile page
await test.expect(page.url()).toMatch(/\/marketplace\/creator\/.*/);
await test.expect(creatorProfilePage.isLoaded()).resolves.toBeTruthy();
}
});
});
test.describe("Navigation and Responsiveness", () => {
test("navigation bar works correctly", async ({ page }) => {
// Test navigation links
await marketplacePage.navbar.clickMarketplaceLink();
await test.expect(page).toHaveURL(/.*\/marketplace/);
await marketplacePage.navbar.clickBuildLink();
await test.expect(page).toHaveURL(/.*\/build/);
await marketplacePage.navbar.clickMonitorLink();
await test.expect(page).toHaveURL(/.*\/library/);
// Navigate back to marketplace
await page.goto("/marketplace");
await marketplacePage.waitForPageLoad();
});
test("page scrolling works correctly", async () => {
await marketplacePage.scrollToSection("Featured Creators");
await marketplacePage.scrollToSection("Become a Creator");
// Verify sections are accessible
await test
.expect(marketplacePage.hasFeaturedCreatorsSection())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.hasBecomeCreatorSection())
.resolves.toBeTruthy();
});
test("become creator button is functional", async () => {
await test
.expect(marketplacePage.hasBecomeCreatorSection())
.resolves.toBeTruthy();
// Verify button is clickable (without actually clicking to avoid navigation)
await test
.expect(marketplacePage.becomeCreatorButton.isVisible())
.resolves.toBeTruthy();
await test
.expect(marketplacePage.becomeCreatorButton.isEnabled())
.resolves.toBeTruthy();
});
});
test.describe("Performance and Metrics", () => {
test("page loads with expected content metrics", async () => {
const metrics = await marketplacePage.getPageLoadMetrics();
await test.expect(metrics.agentCount).toBeGreaterThan(0);
await test.expect(metrics.creatorCount).toBeGreaterThan(0);
await test.expect(metrics.categoryCount).toBeGreaterThan(0);
console.log("Page Metrics:", metrics);
});
test("agents load within reasonable time", async ({
page: _,
}, testInfo) => {
const startTime = Date.now();
await marketplacePage.waitForAgentsToLoad();
const loadTime = Date.now() - startTime;
// Agents should load within 10 seconds
await test.expect(loadTime).toBeLessThan(10000);
testInfo.attach("load-time", { body: `Agents loaded in ${loadTime}ms` });
});
});
test.describe("Error Handling", () => {
test("handles empty search gracefully", async ({ page }) => {
await marketplacePage.searchAgents("");
await page.waitForTimeout(1000);
// Page should still be functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
});
test("handles invalid category selection gracefully", async ({ page }) => {
try {
await marketplacePage.clickCategory("NonExistentCategory");
await page.waitForTimeout(1000);
// Page should still be functional
await test.expect(marketplacePage.isLoaded()).resolves.toBeTruthy();
} catch (error) {
// This is expected for non-existent categories
console.log("Expected error for non-existent category:", error);
}
});
});
test.describe("Accessibility", () => {
test("main headings are accessible", async ({ page }) => {
const mainHeading = page.getByRole("heading", {
name: "Explore AI agents built for you by the community",
});
await test.expect(mainHeading).toBeVisible();
const featuredHeading = page.getByRole("heading", {
name: "Featured agents",
});
await test.expect(featuredHeading).toBeVisible();
const topAgentsHeading = page.getByRole("heading", {
name: "Top Agents",
});
await test.expect(topAgentsHeading).toBeVisible();
});
test("search input has proper accessibility attributes", async () => {
const searchInput = marketplacePage.searchInput;
await test.expect(searchInput).toBeVisible();
// Check if input has placeholder or label
const placeholder = await searchInput.getAttribute("placeholder");
await test.expect(placeholder).toBeTruthy();
});
test("agent cards are keyboard accessible", async ({ page }) => {
// Focus on first agent card
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
// Should be able to navigate with keyboard
const focusedElement = await page.evaluate(
() => document.activeElement?.tagName,
);
console.log("Focused element:", focusedElement);
});
});
});

View File

@@ -0,0 +1,367 @@
import { Page, Locator } from "@playwright/test";
import { BasePage } from "./base.page";
export interface AgentDetails {
name: string;
creator: string;
description: string;
rating: number;
runs: number;
categories: string[];
version: string;
lastUpdated: string;
}
export interface RelatedAgent {
name: string;
creator: string;
description: string;
rating: number;
runs: number;
}
export class AgentDetailPage extends BasePage {
constructor(page: Page) {
super(page);
}
// Locators
get agentName(): Locator {
return this.page.locator("h1, h2, h3").first();
}
get creatorLink(): Locator {
return this.page.locator('a[href*="/marketplace/creator/"]');
}
get agentDescription(): Locator {
return this.page.locator('div:has-text("Description")').locator("+ div, + p");
}
get downloadButton(): Locator {
return this.page.getByRole("button", { name: "Download agent" });
}
get ratingSection(): Locator {
return this.page.locator('div:has(img[alt*="Icon"])').first();
}
get runsCount(): Locator {
return this.page.locator('div:has-text("runs")');
}
get categoriesSection(): Locator {
return this.page.locator('div:has-text("Categories")').locator("+ div");
}
get versionInfo(): Locator {
return this.page.locator('div:has-text("Version")');
}
get lastUpdatedInfo(): Locator {
return this.page.locator('div:has-text("Last updated")');
}
get breadcrumbNavigation(): Locator {
return this.page.locator('nav, div').filter({ hasText: /Marketplace.*\/.*\/.*/ });
}
get agentImages(): Locator {
return this.page.locator('img[alt*="Image"]');
}
get otherAgentsByCreatorSection(): Locator {
return this.page.locator('h2:has-text("Other agents by")').locator("..");
}
get similarAgentsSection(): Locator {
return this.page.locator('h2:has-text("Similar agents")').locator("..");
}
get relatedAgentCards(): Locator {
return this.page.locator('button[data-testid*="agent-card"]');
}
// Page load and validation
async isLoaded(): Promise<boolean> {
console.log("Checking if agent detail page is loaded");
try {
await this.page.waitForLoadState("domcontentloaded", { timeout: 10_000 });
// Check for agent name
await this.agentName.waitFor({ state: "visible", timeout: 10_000 });
// Check for download button
await this.downloadButton.waitFor({ state: "visible", timeout: 5_000 });
return true;
} catch (error) {
console.error("Error checking if agent detail page is loaded:", error);
return false;
}
}
async hasCorrectURL(creator: string, agentName: string): Promise<boolean> {
const url = this.page.url();
const expectedPattern = `/marketplace/agent/${creator}/${agentName.toLowerCase().replace(/\s+/g, '-')}`;
return url.includes(expectedPattern) || url.includes(`/marketplace/agent/${creator}/`);
}
async hasCorrectTitle(): Promise<boolean> {
const title = await this.page.title();
return title.includes("AutoGPT") && (title.includes("Marketplace") || title.includes("Store"));
}
// Content extraction
async getAgentDetails(): Promise<AgentDetails> {
console.log("Extracting agent details");
const name = (await this.agentName.textContent())?.trim() || "";
const creatorText = await this.creatorLink.textContent();
const creator = creatorText?.trim() || "";
const description = (await this.agentDescription.textContent())?.trim() || "";
// Extract rating
let rating = 0;
try {
const ratingText = await this.ratingSection.textContent();
const ratingMatch = ratingText?.match(/(\d+\.?\d*)/);
rating = ratingMatch ? parseFloat(ratingMatch[1]) : 0;
} catch (error) {
console.log("Could not extract rating:", error);
}
// Extract runs count
let runs = 0;
try {
const runsText = await this.runsCount.textContent();
const runsMatch = runsText?.match(/(\d+)\s*runs/);
runs = runsMatch ? parseInt(runsMatch[1]) : 0;
} catch (error) {
console.log("Could not extract runs count:", error);
}
// Extract categories
let categories: string[] = [];
try {
const categoriesText = await this.categoriesSection.textContent();
categories = categoriesText ? categoriesText.split(/[,\s]+/).filter(c => c.trim()) : [];
} catch (error) {
console.log("Could not extract categories:", error);
}
// Extract version
let version = "";
try {
const versionText = await this.versionInfo.textContent();
const versionMatch = versionText?.match(/Version\s+(\d+)/);
version = versionMatch ? versionMatch[1] : "";
} catch (error) {
console.log("Could not extract version:", error);
}
// Extract last updated
let lastUpdated = "";
try {
const lastUpdatedText = await this.lastUpdatedInfo.textContent();
lastUpdated = lastUpdatedText?.replace("Last updated", "").trim() || "";
} catch (error) {
console.log("Could not extract last updated:", error);
}
return {
name,
creator,
description,
rating,
runs,
categories,
version,
lastUpdated,
};
}
async getRelatedAgents(): Promise<RelatedAgent[]> {
console.log("Getting related agents");
const relatedAgents: RelatedAgent[] = [];
const agentCards = await this.relatedAgentCards.all();
for (const card of agentCards) {
try {
const nameElement = await card.locator("h3").first();
const name = (await nameElement.textContent())?.trim() || "";
const creatorElement = await card.locator('p:has-text("by ")').first();
const creatorText = await creatorElement.textContent();
const creator = creatorText?.replace("by ", "").trim() || "";
const descriptionElement = await card.locator("p").nth(1);
const description = (await descriptionElement.textContent())?.trim() || "";
// Extract rating
let rating = 0;
try {
const ratingElement = await card.locator('div:has-text(".")').first();
const ratingText = await ratingElement.textContent();
const ratingMatch = ratingText?.match(/(\d+\.?\d*)/);
rating = ratingMatch ? parseFloat(ratingMatch[1]) : 0;
} catch {
// Rating extraction failed, use default
}
// Extract runs
let runs = 0;
try {
const runsElement = await card.locator('div:has-text("runs")');
const runsText = await runsElement.textContent();
const runsMatch = runsText?.match(/(\d+)\s*runs/);
runs = runsMatch ? parseInt(runsMatch[1]) : 0;
} catch {
// Runs extraction failed, use default
}
if (name) {
relatedAgents.push({
name,
creator,
description,
rating,
runs,
});
}
} catch (error) {
console.error("Error parsing related agent card:", error);
}
}
return relatedAgents;
}
// Interactions
async clickDownloadAgent(): Promise<void> {
console.log("Clicking download agent button");
await this.downloadButton.click();
}
async clickCreatorLink(): Promise<void> {
console.log("Clicking creator link");
await this.creatorLink.click();
}
async clickRelatedAgent(agentName: string): Promise<void> {
console.log(`Clicking related agent: ${agentName}`);
await this.page.getByRole("button", { name: new RegExp(agentName, "i") }).first().click();
}
async navigateBackToMarketplace(): Promise<void> {
console.log("Navigating back to marketplace");
await this.page.getByRole("link", { name: "Marketplace" }).click();
}
// Content validation
async hasAgentName(): Promise<boolean> {
const name = await this.agentName.textContent();
return name !== null && name.trim().length > 0;
}
async hasCreatorInfo(): Promise<boolean> {
return await this.creatorLink.isVisible();
}
async hasDescription(): Promise<boolean> {
const description = await this.agentDescription.textContent();
return description !== null && description.trim().length > 0;
}
async hasDownloadButton(): Promise<boolean> {
return await this.downloadButton.isVisible();
}
async hasRatingInfo(): Promise<boolean> {
return await this.ratingSection.isVisible();
}
async hasRunsInfo(): Promise<boolean> {
return await this.runsCount.isVisible();
}
async hasCategoriesInfo(): Promise<boolean> {
try {
return await this.categoriesSection.isVisible();
} catch {
return false;
}
}
async hasVersionInfo(): Promise<boolean> {
try {
return await this.versionInfo.isVisible();
} catch {
return false;
}
}
async hasAgentImages(): Promise<boolean> {
const images = await this.agentImages.count();
return images > 0;
}
async hasBreadcrumbNavigation(): Promise<boolean> {
try {
return await this.breadcrumbNavigation.isVisible();
} catch {
return false;
}
}
async hasOtherAgentsByCreatorSection(): Promise<boolean> {
try {
return await this.otherAgentsByCreatorSection.isVisible();
} catch {
return false;
}
}
async hasSimilarAgentsSection(): Promise<boolean> {
try {
return await this.similarAgentsSection.isVisible();
} catch {
return false;
}
}
// Utility methods
async scrollToSection(sectionName: string): Promise<void> {
console.log(`Scrolling to section: ${sectionName}`);
await this.page.getByRole("heading", { name: new RegExp(sectionName, "i") }).scrollIntoViewIfNeeded();
}
async waitForImagesLoad(): Promise<void> {
console.log("Waiting for images to load");
await this.page.waitForLoadState("networkidle", { timeout: 10_000 });
}
async getPageMetrics(): Promise<{
hasAllRequiredElements: boolean;
relatedAgentsCount: number;
imageCount: number;
}> {
const relatedAgents = await this.getRelatedAgents();
const imageCount = await this.agentImages.count();
const hasAllRequiredElements =
await this.hasAgentName() &&
await this.hasCreatorInfo() &&
await this.hasDescription() &&
await this.hasDownloadButton();
return {
hasAllRequiredElements,
relatedAgentsCount: relatedAgents.length,
imageCount,
};
}
}

View File

@@ -0,0 +1,392 @@
import { Page, Locator } from "@playwright/test";
import { BasePage } from "./base.page";
export interface CreatorProfile {
username: string;
displayName: string;
handle: string;
description: string;
agentCount: number;
averageRating: number;
totalRuns: number;
topCategories: string[];
}
export interface CreatorAgent {
name: string;
description: string;
rating: number;
runs: number;
imageUrl?: string;
}
export class CreatorProfilePage extends BasePage {
constructor(page: Page) {
super(page);
}
// Locators
get creatorDisplayName(): Locator {
return this.page.locator("h1").first();
}
get creatorHandle(): Locator {
return this.page.locator('div:has-text("@")').first();
}
get creatorAvatar(): Locator {
return this.page.locator('img[alt*="avatar"], img[alt*="profile"]').first();
}
get creatorDescription(): Locator {
return this.page
.locator("p, div")
.filter({ hasText: /About|Description/ })
.locator("+ p, + div");
}
get aboutSection(): Locator {
return this.page.locator('p:has-text("About")').locator("..");
}
get topCategoriesSection(): Locator {
return this.page.locator('div:has-text("Top categories")').locator("..");
}
get averageRatingSection(): Locator {
return this.page.locator('div:has-text("Average rating")').locator("..");
}
get totalRunsSection(): Locator {
return this.page.locator('div:has-text("Number of runs")').locator("..");
}
get agentsSection(): Locator {
return this.page.locator('h2:has-text("Agents by")').locator("..");
}
get agentCards(): Locator {
return this.page.locator('button[data-testid*="agent-card"]');
}
get breadcrumbNavigation(): Locator {
return this.page.locator("nav, div").filter({ hasText: /Store.*\/.*/ });
}
get categoryTags(): Locator {
return this.topCategoriesSection.locator("li, span");
}
// Page load and validation
async isLoaded(): Promise<boolean> {
console.log("Checking if creator profile page is loaded");
try {
await this.page.waitForLoadState("domcontentloaded", { timeout: 10_000 });
// Check for creator display name
await this.creatorDisplayName.waitFor({
state: "visible",
timeout: 10_000,
});
// Check for agents section
await this.agentsSection.waitFor({ state: "visible", timeout: 5_000 });
return true;
} catch (error) {
console.error("Error checking if creator profile page is loaded:", error);
return false;
}
}
async hasCorrectURL(creatorHandle: string): Promise<boolean> {
const url = this.page.url();
return url.includes(`/marketplace/creator/${creatorHandle}`);
}
async hasCorrectTitle(): Promise<boolean> {
const title = await this.page.title();
return (
title.includes("AutoGPT Store") || title.includes("AutoGPT Marketplace")
);
}
// Content extraction
async getCreatorProfile(): Promise<CreatorProfile> {
console.log("Extracting creator profile information");
const displayName =
(await this.creatorDisplayName.textContent())?.trim() || "";
let handle = "";
try {
const handleText = await this.creatorHandle.textContent();
handle = handleText?.replace("@", "").trim() || "";
} catch (error) {
console.log("Could not extract handle:", error);
}
let description = "";
try {
description = (await this.creatorDescription.textContent())?.trim() || "";
} catch (error) {
console.log("Could not extract description:", error);
}
// Extract average rating
let averageRating = 0;
try {
const ratingText = await this.averageRatingSection.textContent();
const ratingMatch = ratingText?.match(/(\d+\.?\d*)/);
averageRating = ratingMatch ? parseFloat(ratingMatch[1]) : 0;
} catch (error) {
console.log("Could not extract average rating:", error);
}
// Extract total runs
let totalRuns = 0;
try {
const runsText = await this.totalRunsSection.textContent();
const runsMatch = runsText?.match(/(\d+)\s*runs?/);
totalRuns = runsMatch ? parseInt(runsMatch[1]) : 0;
} catch (error) {
console.log("Could not extract total runs:", error);
}
// Extract top categories
const topCategories: string[] = [];
try {
const categoryElements = await this.categoryTags.all();
for (const element of categoryElements) {
const categoryText = await element.textContent();
if (categoryText && categoryText.trim()) {
topCategories.push(categoryText.trim());
}
}
} catch (error) {
console.log("Could not extract categories:", error);
}
// Count agents
const agentCount = await this.agentCards.count();
return {
username: handle || displayName.toLowerCase().replace(/\s+/g, "-"),
displayName,
handle,
description,
agentCount,
averageRating,
totalRuns,
topCategories,
};
}
async getCreatorAgents(): Promise<CreatorAgent[]> {
console.log("Getting creator's agents");
const agents: CreatorAgent[] = [];
const agentCards = await this.agentCards.all();
for (const card of agentCards) {
try {
const nameElement = await card.locator("h3").first();
const name = (await nameElement.textContent())?.trim() || "";
const descriptionElement = await card.locator("p").first();
const description =
(await descriptionElement.textContent())?.trim() || "";
// Extract rating
let rating = 0;
try {
const ratingElement = await card.locator('div:has-text(".")').first();
const ratingText = await ratingElement.textContent();
const ratingMatch = ratingText?.match(/(\d+\.?\d*)/);
rating = ratingMatch ? parseFloat(ratingMatch[1]) : 0;
} catch {
// Rating extraction failed, use default
}
// Extract runs
let runs = 0;
try {
const runsElement = await card.locator('div:has-text("runs")');
const runsText = await runsElement.textContent();
const runsMatch = runsText?.match(/(\d+)\s*runs/);
runs = runsMatch ? parseInt(runsMatch[1]) : 0;
} catch {
// Runs extraction failed, use default
}
// Extract image URL
let imageUrl = "";
try {
const imageElement = await card.locator("img").first();
imageUrl = (await imageElement.getAttribute("src")) || "";
} catch {
// Image extraction failed, use default
}
if (name) {
agents.push({
name,
description,
rating,
runs,
imageUrl,
});
}
} catch (error) {
console.error("Error parsing agent card:", error);
}
}
return agents;
}
// Interactions
async clickAgent(agentName: string): Promise<void> {
console.log(`Clicking agent: ${agentName}`);
await this.page
.getByRole("button", { name: new RegExp(agentName, "i") })
.first()
.click();
}
async navigateBackToStore(): Promise<void> {
console.log("Navigating back to store");
await this.page.getByRole("link", { name: "Store" }).click();
}
async scrollToAgentsSection(): Promise<void> {
console.log("Scrolling to agents section");
await this.agentsSection.scrollIntoViewIfNeeded();
}
// Content validation
async hasCreatorDisplayName(): Promise<boolean> {
const name = await this.creatorDisplayName.textContent();
return name !== null && name.trim().length > 0;
}
async hasCreatorHandle(): Promise<boolean> {
try {
return await this.creatorHandle.isVisible();
} catch {
return false;
}
}
async hasCreatorAvatar(): Promise<boolean> {
try {
return await this.creatorAvatar.isVisible();
} catch {
return false;
}
}
async hasCreatorDescription(): Promise<boolean> {
try {
const description = await this.creatorDescription.textContent();
return description !== null && description.trim().length > 0;
} catch {
return false;
}
}
async hasTopCategoriesSection(): Promise<boolean> {
try {
return await this.topCategoriesSection.isVisible();
} catch {
return false;
}
}
async hasAverageRatingSection(): Promise<boolean> {
try {
return await this.averageRatingSection.isVisible();
} catch {
return false;
}
}
async hasTotalRunsSection(): Promise<boolean> {
try {
return await this.totalRunsSection.isVisible();
} catch {
return false;
}
}
async hasAgentsSection(): Promise<boolean> {
return await this.agentsSection.isVisible();
}
async hasAgents(): Promise<boolean> {
const agentCount = await this.agentCards.count();
return agentCount > 0;
}
async hasBreadcrumbNavigation(): Promise<boolean> {
try {
return await this.breadcrumbNavigation.isVisible();
} catch {
return false;
}
}
// Utility methods
async waitForAgentsLoad(): Promise<void> {
console.log("Waiting for creator's agents to load");
try {
await this.page.waitForSelector('button[data-testid*="agent-card"]', {
timeout: 10_000,
});
} catch {
console.log("No agent cards found or timeout reached");
}
}
async getPageMetrics(): Promise<{
hasAllRequiredElements: boolean;
agentCount: number;
categoryCount: number;
hasProfileInfo: boolean;
}> {
const agents = await this.getCreatorAgents();
const profile = await this.getCreatorProfile();
const hasAllRequiredElements =
(await this.hasCreatorDisplayName()) && (await this.hasAgentsSection());
const hasProfileInfo =
(await this.hasCreatorDescription()) ||
(await this.hasAverageRatingSection()) ||
(await this.hasTotalRunsSection());
return {
hasAllRequiredElements,
agentCount: agents.length,
categoryCount: profile.topCategories.length,
hasProfileInfo,
};
}
async searchCreatorAgents(query: string): Promise<CreatorAgent[]> {
console.log(`Searching creator's agents for: ${query}`);
const allAgents = await this.getCreatorAgents();
return allAgents.filter(
(agent) =>
agent.name.toLowerCase().includes(query.toLowerCase()) ||
agent.description.toLowerCase().includes(query.toLowerCase()),
);
}
async getAgentsByCategory(category: string): Promise<CreatorAgent[]> {
console.log(`Getting creator's agents by category: ${category}`);
// This would require additional DOM structure to filter by category
// For now, return all agents
return await this.getCreatorAgents();
}
}

View File

@@ -0,0 +1,351 @@
import { Page, Locator } from "@playwright/test";
import { BasePage } from "./base.page";
export interface Agent {
id: string;
name: string;
description: string;
creator: string;
rating: number;
runs: number;
categories?: string[];
}
export interface Creator {
username: string;
displayName: string;
description: string;
agentCount: number;
rating?: number;
categories?: string[];
}
export class MarketplacePage extends BasePage {
constructor(page: Page) {
super(page);
}
// Locators
get searchInput(): Locator {
return this.page.getByTestId("store-search-input");
}
get categoryButtons(): Locator {
return this.page.locator('[data-testid*="category-"]');
}
get featuredAgentsSection(): Locator {
return this.page.locator('h2:has-text("Featured agents")').locator("..");
}
get topAgentsSection(): Locator {
return this.page.locator('h2:has-text("Top Agents")').locator("..");
}
get featuredCreatorsSection(): Locator {
return this.page.locator('h2:has-text("Featured Creators")').locator("..");
}
get becomeCreatorButton(): Locator {
return this.page.getByRole("button", { name: "Become a Creator" });
}
get agentCards(): Locator {
return this.page.locator('button[data-testid*="agent-card"]');
}
get featuredAgentLinks(): Locator {
return this.featuredAgentsSection.locator("a");
}
// Page load check
async isLoaded(): Promise<boolean> {
console.log("Checking if marketplace page is loaded");
try {
await this.page.waitForLoadState("domcontentloaded", { timeout: 10_000 });
// Check for main heading
await this.page
.getByRole("heading", {
name: "Explore AI agents built for you by the community",
})
.waitFor({ state: "visible", timeout: 10_000 });
// Check for search input
await this.searchInput.waitFor({ state: "visible", timeout: 5_000 });
return true;
} catch (error) {
console.error("Error checking if marketplace page is loaded:", error);
return false;
}
}
// Search functionality
async searchAgents(query: string): Promise<void> {
console.log(`Searching for agents with query: ${query}`);
await this.searchInput.fill(query);
await this.searchInput.press("Enter");
// Wait for search results to load
await this.page.waitForTimeout(1000);
}
async clearSearch(): Promise<void> {
console.log("Clearing search input");
await this.searchInput.clear();
}
// Category filtering
async clickCategory(categoryName: string): Promise<void> {
console.log(`Clicking category: ${categoryName}`);
await this.page.locator(`text=${categoryName}`).first().click();
await this.page.waitForTimeout(1000);
}
async getAvailableCategories(): Promise<string[]> {
console.log("Getting available categories");
const categories = await this.page
.locator('div[role="button"]')
.allTextContents();
return categories.filter((cat) => cat.trim().length > 0);
}
// Agent interactions
async getAgentCards(): Promise<Agent[]> {
console.log("Getting agent cards from marketplace");
const agents: Agent[] = [];
// Get agent cards from both sections
const topAgentCards = await this.topAgentsSection
.locator('button[data-testid*="agent-card"]')
.all();
for (const card of topAgentCards) {
try {
const nameElement = await card.locator("h3").first();
const name = await nameElement.textContent();
const creatorElement = await card.locator('p:has-text("by ")').first();
const creatorText = await creatorElement.textContent();
const creator = creatorText?.replace("by ", "") || "";
const descriptionElement = await card.locator("p").nth(1);
const description = await descriptionElement.textContent();
const runsElement = await card.locator('div:has-text("runs")');
const runsText = await runsElement.textContent();
const runs = parseInt(runsText?.match(/\d+/)?.[0] || "0");
// Try to get rating
const ratingElement = await card.locator('div:has-text(".")').first();
const ratingText = await ratingElement.textContent();
const rating = parseFloat(ratingText?.match(/\d+\.\d+/)?.[0] || "0");
if (name) {
agents.push({
id: (await card.getAttribute("data-testid")) || "",
name: name.trim(),
description: description?.trim() || "",
creator: creator.trim(),
rating,
runs,
});
}
} catch (error) {
console.error("Error parsing agent card:", error);
}
}
return agents;
}
async getFeaturedAgents(): Promise<Agent[]> {
console.log("Getting featured agents");
const agents: Agent[] = [];
const featuredLinks = await this.featuredAgentLinks.all();
for (const link of featuredLinks) {
try {
const nameElement = await link.locator("h3").first();
const name = await nameElement.textContent();
const creatorElement = await link.locator('p:has-text("By ")').first();
const creatorText = await creatorElement.textContent();
const creator = creatorText?.replace("By ", "") || "";
const descriptionElement = await link.locator("p").nth(1);
const description = await descriptionElement.textContent();
const runsElement = await link.locator('div:has-text("runs")');
const runsText = await runsElement.textContent();
const runs = parseInt(runsText?.match(/\d+/)?.[0] || "0");
const ratingElement = await link.locator('p:has-text(".")').first();
const ratingText = await ratingElement.textContent();
const rating = parseFloat(ratingText?.match(/\d+\.\d+/)?.[0] || "0");
if (name) {
agents.push({
id: (await link.getAttribute("href")) || "",
name: name.trim(),
description: description?.trim() || "",
creator: creator.trim(),
rating,
runs,
});
}
} catch (error) {
console.error("Error parsing featured agent:", error);
}
}
return agents;
}
async clickAgentCard(agentName: string): Promise<void> {
console.log(`Clicking agent card: ${agentName}`);
await this.page
.getByRole("button", { name: new RegExp(agentName, "i") })
.first()
.click();
}
async clickFeaturedAgent(agentName: string): Promise<void> {
console.log(`Clicking featured agent: ${agentName}`);
await this.featuredAgentsSection
.getByRole("link", { name: new RegExp(agentName, "i") })
.first()
.click();
}
// Creator interactions
async getFeaturedCreators(): Promise<Creator[]> {
console.log("Getting featured creators");
const creators: Creator[] = [];
const creatorElements = await this.featuredCreatorsSection
.locator("div")
.all();
for (const element of creatorElements) {
try {
const nameElement = await element.locator("h3").first();
const name = await nameElement.textContent();
const descriptionElement = await element.locator("p").first();
const description = await descriptionElement.textContent();
const agentCountElement = await element.locator(
'div:has-text("agents")',
);
const agentCountText = await agentCountElement.textContent();
const agentCount = parseInt(agentCountText?.match(/\d+/)?.[0] || "0");
if (name && description) {
creators.push({
username: name.trim(),
displayName: name.trim(),
description: description.trim(),
agentCount,
});
}
} catch (error) {
console.error("Error parsing creator:", error);
}
}
return creators;
}
async clickCreator(creatorName: string): Promise<void> {
console.log(`Clicking creator: ${creatorName}`);
await this.page.getByRole("heading", { name: creatorName }).click();
}
// Navigation checks
async hasCorrectTitle(): Promise<boolean> {
const title = await this.page.title();
return title.includes("Marketplace") || title.includes("AutoGPT Platform");
}
async hasCorrectURL(): Promise<boolean> {
const url = this.page.url();
return url.includes("/marketplace");
}
// Content checks
async hasMainHeading(): Promise<boolean> {
try {
await this.page
.getByRole("heading", {
name: "Explore AI agents built for you by the community",
})
.waitFor({ state: "visible", timeout: 5_000 });
return true;
} catch {
return false;
}
}
async hasSearchInput(): Promise<boolean> {
return await this.searchInput.isVisible();
}
async hasCategoryButtons(): Promise<boolean> {
const categories = await this.getAvailableCategories();
return categories.length > 0;
}
async hasFeaturedAgentsSection(): Promise<boolean> {
return await this.featuredAgentsSection.isVisible();
}
async hasTopAgentsSection(): Promise<boolean> {
return await this.topAgentsSection.isVisible();
}
async hasFeaturedCreatorsSection(): Promise<boolean> {
return await this.featuredCreatorsSection.isVisible();
}
async hasBecomeCreatorSection(): Promise<boolean> {
return await this.becomeCreatorButton.isVisible();
}
// Utility methods
async scrollToSection(sectionName: string): Promise<void> {
console.log(`Scrolling to section: ${sectionName}`);
await this.page
.getByRole("heading", { name: sectionName })
.scrollIntoViewIfNeeded();
}
async waitForAgentsToLoad(): Promise<void> {
console.log("Waiting for agents to load");
await this.page.waitForSelector('button[data-testid*="agent-card"]', {
timeout: 10_000,
});
}
async hasAgentCards(): Promise<boolean> {
const agents = await this.getAgentCards();
return agents.length > 0;
}
async getPageLoadMetrics(): Promise<{
agentCount: number;
creatorCount: number;
categoryCount: number;
}> {
const agents = await this.getAgentCards();
const creators = await this.getFeaturedCreators();
const categories = await this.getAvailableCategories();
return {
agentCount: agents.length,
creatorCount: creators.length,
categoryCount: categories.length,
};
}
}