✅ Add Playwright E2E testing setup to seo-admin frontend
- Add @playwright/test dependency - Add test:e2e scripts - Add playwright.config.ts - Add pipeline-jobs.spec.ts test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
86280033c3
commit
d162a4cd7c
3 changed files with 257 additions and 1 deletions
218
features/seo/frontend-admin/e2e/tests/pipeline-jobs.spec.ts
Normal file
218
features/seo/frontend-admin/e2e/tests/pipeline-jobs.spec.ts
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* E2E Tests for Pipeline Jobs Page (SEO Admin)
|
||||
*
|
||||
* Tests the Pipeline Jobs tab for queuing SEO generation jobs.
|
||||
* Note: These tests require the SEO backend API to be running at localhost:4001
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Pipeline Jobs Page', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Navigate to SEO admin and click Pipeline Jobs tab
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Pipeline Jobs' }).click();
|
||||
// Wait for stats to load
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
test('displays pipeline jobs heading', async ({ page }) => {
|
||||
await expect(page.getByRole('heading', { name: 'Pipeline Jobs' })).toBeVisible();
|
||||
await expect(page.getByText('Queue and monitor SEO generation jobs')).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays queue stats cards', async ({ page }) => {
|
||||
// Check all stat labels are present
|
||||
await expect(page.getByText('Waiting')).toBeVisible();
|
||||
await expect(page.getByText('Active')).toBeVisible();
|
||||
await expect(page.getByText('Delayed')).toBeVisible();
|
||||
await expect(page.getByText('Completed')).toBeVisible();
|
||||
await expect(page.getByText('Failed')).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays marketplace images section', async ({ page }) => {
|
||||
await expect(page.getByRole('heading', { name: 'Marketplace Images' })).toBeVisible();
|
||||
await expect(page.getByText('Deployment ID')).toBeVisible();
|
||||
await expect(page.getByText('All Categories')).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays single page generation section', async ({ page }) => {
|
||||
await expect(page.getByRole('heading', { name: 'Single Page Generation' })).toBeVisible();
|
||||
await expect(page.getByText('Category')).toBeVisible();
|
||||
await expect(page.getByText('City Slug')).toBeVisible();
|
||||
await expect(page.getByText('Filters')).toBeVisible();
|
||||
await expect(page.getByText('Generate Images')).toBeVisible();
|
||||
});
|
||||
|
||||
test('has default deployment ID value', async ({ page }) => {
|
||||
const deploymentInput = page.locator('input[type="text"]').first();
|
||||
await expect(deploymentInput).toHaveValue('marketplace-trustedmeet');
|
||||
});
|
||||
|
||||
test('all categories checkbox is checked by default', async ({ page }) => {
|
||||
const allCategoriesCheckbox = page.locator('input[type="checkbox"]').first();
|
||||
await expect(allCategoriesCheckbox).toBeChecked();
|
||||
});
|
||||
|
||||
test('generate images checkbox is checked by default', async ({ page }) => {
|
||||
const generateImagesCheckbox = page.getByText('Generate Images').locator('..').locator('input[type="checkbox"]');
|
||||
await expect(generateImagesCheckbox).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Category Selection', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Pipeline Jobs' }).click();
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
test('shows category chips when unchecking all categories', async ({ page }) => {
|
||||
// Uncheck "All Categories"
|
||||
const allCategoriesCheckbox = page.locator('input[type="checkbox"]').first();
|
||||
await allCategoriesCheckbox.uncheck();
|
||||
|
||||
// Category chips should appear
|
||||
await expect(page.getByRole('button', { name: 'escorts' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'gfe' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('can select individual categories', async ({ page }) => {
|
||||
// Uncheck "All Categories"
|
||||
const allCategoriesCheckbox = page.locator('input[type="checkbox"]').first();
|
||||
await allCategoriesCheckbox.uncheck();
|
||||
|
||||
// Click on escorts category
|
||||
const escortsButton = page.getByRole('button', { name: 'escorts' });
|
||||
await escortsButton.click();
|
||||
|
||||
// Button should change style to indicate selection
|
||||
await expect(escortsButton).toHaveCSS('background-color', 'rgb(0, 170, 255)');
|
||||
});
|
||||
|
||||
test('queue button shows selected category count', async ({ page }) => {
|
||||
// Uncheck "All Categories"
|
||||
const allCategoriesCheckbox = page.locator('input[type="checkbox"]').first();
|
||||
await allCategoriesCheckbox.uncheck();
|
||||
|
||||
// Select two categories
|
||||
await page.getByRole('button', { name: 'escorts' }).click();
|
||||
await page.getByRole('button', { name: 'gfe' }).click();
|
||||
|
||||
// Queue button should show count
|
||||
await expect(page.getByRole('button', { name: /Queue 2 Categories/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Single Page Form', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Pipeline Jobs' }).click();
|
||||
await page.waitForTimeout(500);
|
||||
});
|
||||
|
||||
test('has default city value', async ({ page }) => {
|
||||
const cityInput = page.locator('input[placeholder*="new-york"]');
|
||||
await expect(cityInput).toHaveValue('new-york');
|
||||
});
|
||||
|
||||
test('can change city slug', async ({ page }) => {
|
||||
const cityInput = page.locator('input[placeholder*="new-york"]');
|
||||
await cityInput.fill('los-angeles');
|
||||
await expect(cityInput).toHaveValue('los-angeles');
|
||||
});
|
||||
|
||||
test('can add filters', async ({ page }) => {
|
||||
const filtersInput = page.locator('input[placeholder*="blonde"]');
|
||||
await filtersInput.fill('blonde, curvy');
|
||||
await expect(filtersInput).toHaveValue('blonde, curvy');
|
||||
});
|
||||
|
||||
test('can toggle generate images', async ({ page }) => {
|
||||
const generateImagesCheckbox = page.getByText('Generate Images').locator('..').locator('input[type="checkbox"]');
|
||||
|
||||
// Should be checked by default
|
||||
await expect(generateImagesCheckbox).toBeChecked();
|
||||
|
||||
// Uncheck it
|
||||
await generateImagesCheckbox.uncheck();
|
||||
await expect(generateImagesCheckbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
test('category dropdown has options', async ({ page }) => {
|
||||
const categorySelect = page.locator('select').first();
|
||||
|
||||
// Click to open dropdown
|
||||
await categorySelect.click();
|
||||
|
||||
// Should have escorts as default value
|
||||
await expect(categorySelect).toHaveValue('escorts');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Service Health Indicators', () => {
|
||||
test('displays service health status', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Pipeline Jobs' }).click();
|
||||
|
||||
// Wait for health data to load
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Should show Text and Image service indicators
|
||||
await expect(page.getByText('Text:')).toBeVisible();
|
||||
await expect(page.getByText('Image:')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Info Panel', () => {
|
||||
test('displays about section', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Pipeline Jobs' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'About Pipeline Jobs' })).toBeVisible();
|
||||
await expect(page.getByText('Marketplace Images')).toBeVisible();
|
||||
await expect(page.getByText('Single Page')).toBeVisible();
|
||||
await expect(page.getByText('BullMQ')).toBeVisible();
|
||||
});
|
||||
|
||||
test('has link to queues dashboard', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Pipeline Jobs' }).click();
|
||||
|
||||
const queuesLink = page.getByRole('link', { name: 'Queues dashboard' });
|
||||
await expect(queuesLink).toBeVisible();
|
||||
await expect(queuesLink).toHaveAttribute('href', '/queues');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Tab Navigation', () => {
|
||||
test('pipeline jobs tab is clickable', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Verify Pipeline Jobs tab exists
|
||||
const pipelineTab = page.getByRole('button', { name: 'Pipeline Jobs' });
|
||||
await expect(pipelineTab).toBeVisible();
|
||||
|
||||
// Click it
|
||||
await pipelineTab.click();
|
||||
|
||||
// Verify we're on the pipeline page
|
||||
await expect(page.getByRole('heading', { name: 'Pipeline Jobs' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('can navigate between tabs', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Go to Pipeline Jobs
|
||||
await page.getByRole('button', { name: 'Pipeline Jobs' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Pipeline Jobs' })).toBeVisible();
|
||||
|
||||
// Go back to Domain Config
|
||||
await page.getByRole('button', { name: 'Domain Config' }).click();
|
||||
await expect(page.getByRole('heading', { name: /Domain|Config/i })).toBeVisible();
|
||||
|
||||
// Go to Image Generator
|
||||
await page.getByRole('button', { name: 'Image Generator' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Image Generation' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -9,7 +9,10 @@
|
|||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lilith/service-react-bootstrap": "^1.0.0",
|
||||
|
|
@ -20,6 +23,7 @@
|
|||
"react-router-dom": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.49.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
|
|
|
|||
34
features/seo/frontend-admin/playwright.config.ts
Normal file
34
features/seo/frontend-admin/playwright.config.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Playwright E2E Configuration for SEO Admin
|
||||
*/
|
||||
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e/tests',
|
||||
testMatch: /.*\.spec\.ts/,
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: 4,
|
||||
reporter: 'html',
|
||||
outputDir: 'test-results/seo-admin',
|
||||
use: {
|
||||
baseURL: 'http://localhost:4004/admin/seo',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'pnpm dev',
|
||||
port: 4004,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120000,
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue