feat(tests): Add screenshot script and test documentation

Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-26 15:17:38 +00:00
parent 2a86e79c32
commit de39acfa55
3 changed files with 287 additions and 1 deletions

View File

@@ -19,7 +19,8 @@
"test:e2e:debug": "playwright test --debug",
"test:e2e:chromium": "playwright test --project=chromium",
"test:e2e:headed": "playwright test --headed",
"test:e2e:report": "playwright show-report"
"test:e2e:report": "playwright show-report",
"screenshots": "node tests/take-screenshots.mjs"
},
"dependencies": {
"@hookform/resolvers": "^5.2.1",

108
web/tests/README.md Normal file
View File

@@ -0,0 +1,108 @@
# E2E Tests with Playwright
This directory contains end-to-end tests for the Dashboard Icons web application using Playwright.
## Setup
Playwright is already installed. The configuration is in `playwright.config.ts`.
## Running Tests
Run all e2e tests:
```bash
npm run test:e2e
```
Run tests in UI mode (interactive):
```bash
npm run test:e2e:ui
```
Run tests in debug mode:
```bash
npm run test:e2e:debug
```
Run tests only on Chromium:
```bash
npm run test:e2e:chromium
```
Run tests in headed mode (see the browser):
```bash
npm run test:e2e:headed
```
View test report:
```bash
npm run test:e2e:report
```
## Taking Screenshots
To take screenshots of the SVG Customizer feature:
1. Start the dev server (in a separate terminal):
```bash
npm run dev:web
```
2. Run the screenshot script:
```bash
npm run screenshots
```
Screenshots will be saved to `test-results/screenshots/`.
## Test Coverage
### SVG Icon Customizer Tests (`icon-customizer.spec.ts`)
Tests for the inline SVG color customizer feature:
- ✅ Display of "Customize Icon" button on icon detail pages
- ✅ Opening the inline customizer
- ✅ Display of color pickers for detected colors
- ✅ Changing icon colors via color picker
- ✅ Copy and Download buttons functionality
- ✅ Closing the customizer
- ✅ Handling icons with no customizable colors
- ✅ Testing with multiple random icons
- ✅ Info popover about color customization
- ✅ Preview rendering with customized colors
## Screenshots
The following screenshots document the SVG Customizer feature:
1. **01-icon-page-with-customize-button.png** - Icon detail page with "Customize Icon" button
2. **02-inline-customizer-opened.png** - Inline customizer opened with color pickers
3. **04-before-color-change.png** - Customizer state before color changes
4. **06-action-buttons.png** - Copy and Download action buttons
5. **07-github-customizer.png** - Customizer with GitHub icon
6. **07-docker-customizer.png** - Customizer with Docker icon
## Configuration
The Playwright configuration (`playwright.config.ts`) includes:
- **Base URL**: `http://localhost:3000`
- **Test directory**: `./tests`
- **Projects**: Chromium (Firefox and WebKit commented out for faster CI runs)
- **Web server**: Automatically starts Next.js dev server before tests
- **Screenshots**: Taken only on failure
- **Trace**: Recorded on first retry
## CI/CD
Tests are configured to run in CI environments with:
- Retries: 2 on CI, 0 locally
- Workers: 1 on CI (serial execution)
- Parallel tests disabled on CI for stability
## Notes
- Some icons may not have customizable colors (those using strokes or other styling methods)
- The customizer feature extracts fill and stroke colors from SVG files
- Tests are resilient to icons without the customize feature
- Screenshots are excluded from git (see `.gitignore`)

View File

@@ -0,0 +1,177 @@
import { chromium } from '@playwright/test';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const SCREENSHOTS_DIR = path.join(__dirname, '..', 'test-results', 'screenshots');
const BASE_URL = 'http://localhost:3000';
// Test icons with different characteristics
const TEST_ICONS = [
'github',
'docker',
'react',
'nextjs',
'typescript',
];
async function takeScreenshots() {
console.log('Starting browser...');
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
});
const page = await context.newPage();
try {
console.log('Taking screenshots of SVG Customizer feature...\n');
// Screenshot 1: Icon page with customize button
console.log('1. Navigating to icon page...');
await page.goto(`${BASE_URL}/icons/${TEST_ICONS[0]}`);
await page.waitForLoadState('networkidle');
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, '01-icon-page-with-customize-button.png'),
fullPage: true
});
console.log('✓ Saved: 01-icon-page-with-customize-button.png');
// Screenshot 2: Opened inline customizer
console.log('\n2. Opening inline customizer...');
const customizeButton = page.getByRole('button', { name: /customize icon/i });
await customizeButton.click();
await page.waitForTimeout(500);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, '02-inline-customizer-opened.png'),
fullPage: true
});
console.log('✓ Saved: 02-inline-customizer-opened.png');
// Screenshot 3: Color pickers displayed
console.log('\n3. Showing color pickers...');
await page.goto(`${BASE_URL}/icons/${TEST_ICONS[2]}`);
await page.waitForLoadState('networkidle');
const customizeButton2 = page.getByRole('button', { name: /customize icon/i });
if (await customizeButton2.isVisible({ timeout: 5000 }).catch(() => false)) {
await customizeButton2.click();
await page.waitForTimeout(500);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, '03-color-pickers-displayed.png'),
fullPage: true
});
console.log('✓ Saved: 03-color-pickers-displayed.png');
} else {
console.log('⚠ Skipped: Customize button not found for', TEST_ICONS[2]);
}
// Screenshot 4: Before color change
console.log('\n4. Taking before color change screenshot...');
await page.goto(`${BASE_URL}/icons/${TEST_ICONS[1]}`);
await page.waitForLoadState('networkidle');
const customizeButton3 = page.getByRole('button', { name: /customize icon/i });
if (await customizeButton3.isVisible({ timeout: 5000 }).catch(() => false)) {
await customizeButton3.click();
await page.waitForTimeout(500);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, '04-before-color-change.png'),
fullPage: true
});
console.log('✓ Saved: 04-before-color-change.png');
} else {
console.log('⚠ Skipped: Customize button not found for', TEST_ICONS[1]);
}
// Screenshot 5: After color change
console.log('\n5. Changing color and taking after screenshot...');
const hueSlider = page.locator('input[type="range"]').first();
if (await hueSlider.isVisible({ timeout: 5000 }).catch(() => false)) {
await hueSlider.fill('180');
await page.waitForTimeout(500);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, '05-after-color-change.png'),
fullPage: true
});
console.log('✓ Saved: 05-after-color-change.png');
} else {
console.log('⚠ Skipped: Color slider not found');
}
// Screenshot 6: Action buttons (copy/download)
console.log('\n6. Showing action buttons...');
await page.goto(`${BASE_URL}/icons/${TEST_ICONS[4]}`);
await page.waitForLoadState('networkidle');
const customizeButton4 = page.getByRole('button', { name: /customize icon/i });
if (await customizeButton4.isVisible({ timeout: 5000 }).catch(() => false)) {
await customizeButton4.click();
await page.waitForTimeout(500);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, '06-action-buttons.png'),
fullPage: true
});
console.log('✓ Saved: 06-action-buttons.png');
} else {
console.log('⚠ Skipped: Customize button not found for', TEST_ICONS[4]);
}
// Screenshot 7: Multiple icons with customizer
console.log('\n7. Testing with multiple icons...');
for (let i = 0; i < 3; i++) {
const iconName = TEST_ICONS[i];
await page.goto(`${BASE_URL}/icons/${iconName}`);
await page.waitForLoadState('networkidle');
const btn = page.getByRole('button', { name: /customize icon/i });
if (await btn.isVisible({ timeout: 5000 }).catch(() => false)) {
await btn.click();
await page.waitForTimeout(500);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, `07-${iconName}-customizer.png`),
fullPage: true
});
console.log(`✓ Saved: 07-${iconName}-customizer.png`);
} else {
console.log(`⚠ Skipped: Customize button not found for ${iconName}`);
}
}
// Screenshot 8: Info popover
console.log('\n8. Showing info popover...');
await page.goto(`${BASE_URL}/icons/${TEST_ICONS[0]}`);
await page.waitForLoadState('networkidle');
const customizeButton5 = page.getByRole('button', { name: /customize icon/i });
if (await customizeButton5.isVisible({ timeout: 5000 }).catch(() => false)) {
await customizeButton5.click();
await page.waitForTimeout(500);
const infoButton = page.getByRole('button', { name: /learn more/i });
if (await infoButton.isVisible({ timeout: 5000 }).catch(() => false)) {
await infoButton.click();
await page.waitForTimeout(300);
await page.screenshot({
path: path.join(SCREENSHOTS_DIR, '08-info-popover.png'),
fullPage: true
});
console.log('✓ Saved: 08-info-popover.png');
} else {
console.log('⚠ Skipped: Info button not found');
}
} else {
console.log('⚠ Skipped: Customize button not found for', TEST_ICONS[0]);
}
console.log('\n✅ All screenshots taken successfully!');
console.log(`📁 Screenshots saved to: ${SCREENSHOTS_DIR}`);
} catch (error) {
console.error('❌ Error taking screenshots:', error);
throw error;
} finally {
await browser.close();
}
}
// Run the screenshot script
takeScreenshots().catch(console.error);