mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
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:
387
autogpt_platform/frontend/src/tests/README.marketplace.md
Normal file
387
autogpt_platform/frontend/src/tests/README.marketplace.md
Normal 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
|
||||
});
|
||||
```
|
||||
331
autogpt_platform/frontend/src/tests/marketplace-agent.spec.ts
Normal file
331
autogpt_platform/frontend/src/tests/marketplace-agent.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
452
autogpt_platform/frontend/src/tests/marketplace-creator.spec.ts
Normal file
452
autogpt_platform/frontend/src/tests/marketplace-creator.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
510
autogpt_platform/frontend/src/tests/marketplace-search.spec.ts
Normal file
510
autogpt_platform/frontend/src/tests/marketplace-search.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
347
autogpt_platform/frontend/src/tests/marketplace.config.ts
Normal file
347
autogpt_platform/frontend/src/tests/marketplace.config.ts
Normal 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",
|
||||
};
|
||||
331
autogpt_platform/frontend/src/tests/marketplace.spec.ts
Normal file
331
autogpt_platform/frontend/src/tests/marketplace.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
367
autogpt_platform/frontend/src/tests/pages/agent-detail.page.ts
Normal file
367
autogpt_platform/frontend/src/tests/pages/agent-detail.page.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
351
autogpt_platform/frontend/src/tests/pages/marketplace.page.ts
Normal file
351
autogpt_platform/frontend/src/tests/pages/marketplace.page.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user