forked from cardosofelipe/fast-next-template
- Remove unused imports (fireEvent, IssueStatus) in issue component tests - Add E2E global type declarations for __TEST_AUTH_STORE__ - Fix toHaveAccessibleName assertion with regex pattern 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
266 lines
9.1 KiB
TypeScript
266 lines
9.1 KiB
TypeScript
/**
|
|
* Issue Management E2E Tests
|
|
*
|
|
* Tests for the issue list and detail pages.
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Issue Management', () => {
|
|
// Use a test project ID
|
|
const projectId = 'test-project-123';
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// Mock authentication - inject test auth store
|
|
await page.addInitScript(() => {
|
|
window.__TEST_AUTH_STORE__ = {
|
|
getState: () => ({
|
|
isAuthenticated: true,
|
|
user: { id: 'test-user', email: 'test@example.com', is_superuser: false },
|
|
accessToken: 'test-token',
|
|
refreshToken: 'test-refresh',
|
|
}),
|
|
};
|
|
});
|
|
});
|
|
|
|
test.describe('Issue List Page', () => {
|
|
test('displays issues list', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
// Wait for the page to load
|
|
await expect(page.getByRole('heading', { name: /issues/i })).toBeVisible();
|
|
|
|
// Should show issue count
|
|
await expect(page.getByText(/issues found/i)).toBeVisible();
|
|
});
|
|
|
|
test('has search functionality', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
const searchInput = page.getByPlaceholder('Search issues...');
|
|
await expect(searchInput).toBeVisible();
|
|
|
|
// Type in search
|
|
await searchInput.fill('authentication');
|
|
|
|
// Wait for debounced search (mock data should filter)
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
test('has status filter', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
// Find status filter
|
|
const statusFilter = page.getByRole('combobox', { name: /filter by status/i });
|
|
await expect(statusFilter).toBeVisible();
|
|
|
|
// Open and select a status
|
|
await statusFilter.click();
|
|
await page.getByRole('option', { name: /in progress/i }).click();
|
|
});
|
|
|
|
test('can toggle extended filters', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
// Extended filters should not be visible initially
|
|
await expect(page.getByLabel('Priority')).not.toBeVisible();
|
|
|
|
// Click filter toggle
|
|
await page.getByRole('button', { name: /toggle extended filters/i }).click();
|
|
|
|
// Extended filters should now be visible
|
|
await expect(page.getByLabel('Priority')).toBeVisible();
|
|
await expect(page.getByLabel('Sprint')).toBeVisible();
|
|
await expect(page.getByLabel('Assignee')).toBeVisible();
|
|
});
|
|
|
|
test('can select issues for bulk actions', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
// Wait for issues to load
|
|
await page.waitForSelector('[data-testid^="issue-row-"]');
|
|
|
|
// Select first issue checkbox
|
|
const firstCheckbox = page.getByRole('checkbox', { name: /select issue/i }).first();
|
|
await firstCheckbox.click();
|
|
|
|
// Bulk actions bar should appear
|
|
await expect(page.getByText('1 selected')).toBeVisible();
|
|
await expect(page.getByRole('button', { name: /change status/i })).toBeVisible();
|
|
});
|
|
|
|
test('navigates to issue detail when clicking row', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
// Wait for issues to load
|
|
await page.waitForSelector('[data-testid^="issue-row-"]');
|
|
|
|
// Click on first issue row
|
|
await page.locator('[data-testid^="issue-row-"]').first().click();
|
|
|
|
// Should navigate to detail page
|
|
await expect(page).toHaveURL(/\/issues\/[^/]+$/);
|
|
});
|
|
|
|
test('has new issue button', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
await expect(page.getByRole('button', { name: /new issue/i })).toBeVisible();
|
|
});
|
|
|
|
test('has sync button', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
await expect(page.getByRole('button', { name: /sync/i })).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Issue Detail Page', () => {
|
|
const issueId = 'ISS-001';
|
|
|
|
test('displays issue details', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Wait for the page to load
|
|
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
|
|
|
// Should show issue number
|
|
await expect(page.getByText(/#\d+/)).toBeVisible();
|
|
});
|
|
|
|
test('displays status badge', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Should show status
|
|
await expect(
|
|
page.getByText(/open|in progress|in review|blocked|done|closed/i).first()
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('displays priority badge', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Should show priority
|
|
await expect(page.getByText(/high|medium|low/i).first()).toBeVisible();
|
|
});
|
|
|
|
test('has back button', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
const backButton = page.getByRole('link', { name: /back to issues/i });
|
|
await expect(backButton).toBeVisible();
|
|
});
|
|
|
|
test('displays status workflow panel', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Should show status workflow
|
|
await expect(page.getByRole('heading', { name: /status workflow/i })).toBeVisible();
|
|
|
|
// Should show all status options
|
|
await expect(page.getByRole('radiogroup', { name: /issue status/i })).toBeVisible();
|
|
});
|
|
|
|
test('displays activity timeline', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Should show activity section
|
|
await expect(page.getByRole('heading', { name: /activity/i })).toBeVisible();
|
|
|
|
// Should have add comment button
|
|
await expect(page.getByRole('button', { name: /add comment/i })).toBeVisible();
|
|
});
|
|
|
|
test('displays issue details panel', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Should show details section
|
|
await expect(page.getByRole('heading', { name: /details/i })).toBeVisible();
|
|
|
|
// Should show assignee info
|
|
await expect(page.getByText(/assignee/i)).toBeVisible();
|
|
});
|
|
|
|
test('can change status via workflow', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Wait for page to load
|
|
await page.waitForSelector('[role="radiogroup"]');
|
|
|
|
// Click on a different status
|
|
const inProgressOption = page.getByRole('radio', { name: /in progress/i });
|
|
await inProgressOption.click();
|
|
|
|
// The status should update (optimistic update)
|
|
await expect(inProgressOption).toHaveAttribute('aria-checked', 'true');
|
|
});
|
|
|
|
test('displays description', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// Should show description heading
|
|
await expect(page.getByRole('heading', { name: /description/i })).toBeVisible();
|
|
});
|
|
|
|
test('shows edit button', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
await expect(page.getByRole('button', { name: /edit/i })).toBeVisible();
|
|
});
|
|
|
|
test('shows external link when available', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/${issueId}`);
|
|
|
|
// The mock data includes an external URL
|
|
await expect(page.getByRole('link', { name: /view in gitea/i })).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Accessibility', () => {
|
|
test('issue list has proper heading structure', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
// Main heading should be h1
|
|
const h1 = page.getByRole('heading', { level: 1 });
|
|
await expect(h1).toBeVisible();
|
|
});
|
|
|
|
test('issue list table has proper ARIA labels', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues`);
|
|
|
|
// Wait for table to load
|
|
await page.waitForSelector('[data-testid^="issue-row-"]');
|
|
|
|
// Checkboxes should have labels
|
|
const checkboxes = page.getByRole('checkbox');
|
|
const count = await checkboxes.count();
|
|
expect(count).toBeGreaterThan(0);
|
|
|
|
// First checkbox should have accessible label
|
|
await expect(checkboxes.first()).toHaveAccessibleName(/.+/);
|
|
});
|
|
|
|
test('issue detail has proper radiogroup for status', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/ISS-001`);
|
|
|
|
// Status workflow should be a radiogroup
|
|
const radiogroup = page.getByRole('radiogroup', { name: /issue status/i });
|
|
await expect(radiogroup).toBeVisible();
|
|
|
|
// Each status should be a radio button
|
|
const radios = page.getByRole('radio');
|
|
const count = await radios.count();
|
|
expect(count).toBe(6); // 6 statuses
|
|
});
|
|
|
|
test('activity timeline has proper list structure', async ({ page }) => {
|
|
await page.goto(`/en/projects/${projectId}/issues/ISS-001`);
|
|
|
|
// Activity should be a list
|
|
const list = page.getByRole('list', { name: /issue activity/i });
|
|
await expect(list).toBeVisible();
|
|
});
|
|
});
|
|
});
|