diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 0ddc215..0e2cbcb 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -20,17 +20,15 @@ jobs: node-version: 20 - name: Install dependencies run: npm ci - - name: Run tests with coverage - run: npm run test:coverage + - name: Install jest-junit for JUnit XML output + run: npm install --save-dev jest-junit + - name: Run tests with coverage and JUnit output + run: | + # Generate JUnit XML file for Test Analytics + JEST_JUNIT_CLASSNAME="{filepath}" npx jest --reporters=jest-junit --coverage --outputFile=junit.xml env: NODE_ENV: test - - name: Generate test results in JUnit format - run: | - # Install jest-junit if not already available - npm install --save-dev jest-junit - # Run tests with JUnit output for better test analytics - npx jest --coverage --testResultsProcessor=jest-junit --outputFile=test-results.xml - continue-on-error: true # Allow flaky tests to not fail the build + # Remove continue-on-error to let tests fail and trigger Test Analytics - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: @@ -41,13 +39,7 @@ jobs: name: codecov-umbrella fail_ci_if_error: false # Don't fail CI if coverage upload fails - name: Upload test results to Codecov - uses: codecov/codecov-action@v5 - if: always() # Run even if tests fail + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - directory: ./ - slug: sfanahata/fitfest - files: ./test-results.xml - flags: test-results - name: test-results - fail_ci_if_error: false diff --git a/app/src/app/activities/page.tsx b/app/src/app/activities/page.tsx index a6c6fc0..e5f36b1 100644 --- a/app/src/app/activities/page.tsx +++ b/app/src/app/activities/page.tsx @@ -63,6 +63,7 @@ export default function ActivitiesPage() {

Your Activities

+ ({activities.length} total) ); + // This will fail because the text doesn't exist + expect(screen.getByText('Click Me Not')).toBeInTheDocument(); + }); + + it('should fail due to array comparison', () => { + const array = [1, 2, 3]; + expect(array).toEqual([1, 2, 3, 4]); + }); + + it('should fail due to object property check', () => { + const obj = { name: 'John', age: 30 }; + expect(obj).toHaveProperty('email'); + }); + + it('should fail due to async operation timeout', async () => { + // This will timeout and fail + await new Promise(resolve => setTimeout(resolve, 2000)); + expect(true).toBe(true); + }, 100); // 100ms timeout + + it('should fail due to exception handling', () => { + const obj: any = null; + // This will throw an exception + expect(obj.nonExistentProperty.value).toBeDefined(); + }); + + it('should fail due to regex mismatch', () => { + const email = 'invalid-email'; + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + expect(emailRegex.test(email)).toBe(true); + }); + + it('should fail due to type assertion', () => { + const value: number = 42; + expect(value).toBe('42'); + }); + + it('should fail due to promise rejection', async () => { + const promise = Promise.reject(new Error('Intentional failure')); + await expect(promise).resolves.toBe('success'); + }); +}); diff --git a/app/src/tests/FlakyTestExample.test.tsx b/app/src/tests/FlakyTestExample.test.tsx index f35ff74..c2ff19d 100644 --- a/app/src/tests/FlakyTestExample.test.tsx +++ b/app/src/tests/FlakyTestExample.test.tsx @@ -5,8 +5,8 @@ import Button from '@/components/Button'; describe('Flaky Test Examples for Codecov Test Analytics', () => { it('should sometimes fail due to timing issues', () => { const random = Math.random(); - // This test will pass consistently but demonstrates timing variability - expect(random).toBeGreaterThan(0); + // This test will fail approximately 20% of the time for flaky detection + expect(random).toBeGreaterThan(0.8); }); it('should fail when environment variables are missing', () => { @@ -48,19 +48,29 @@ describe('Flaky Test Examples for Codecov Test Analytics', () => { document.documentElement.classList.remove('dark'); }); - it('should handle network requests successfully', async () => { - // Mock fetch to always succeed for stable merge + it('should be flaky due to network issues', async () => { + // Mock fetch to sometimes fail for flaky behavior const mockFetch = jest.fn(); global.fetch = mockFetch; - mockFetch.mockResolvedValue({ - ok: true, - json: () => Promise.resolve({ data: 'success' }), - }); + // Simulate network failure 15% of the time + if (Math.random() < 0.15) { + mockFetch.mockRejectedValue(new Error('Network timeout')); + } else { + mockFetch.mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: 'success' }), + }); + } - const response = await fetch('/api/test'); - const data = await response.json(); - expect(data.data).toBe('success'); + try { + const response = await fetch('/api/test'); + const data = await response.json(); + expect(data.data).toBe('success'); + } catch (error) { + // This will fail when network error occurs + expect(error).toBeUndefined(); + } }); it('should validate form validation logic', () => { diff --git a/app/src/tests/TestAnalyticsDemo.test.tsx b/app/src/tests/TestAnalyticsDemo.test.tsx new file mode 100644 index 0000000..3f7303d --- /dev/null +++ b/app/src/tests/TestAnalyticsDemo.test.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import Button from '@/components/Button'; + +describe('Test Analytics Demo - Intentionally Problematic Tests', () => { + it('should fail consistently to demonstrate test failure detection', () => { + // This test will always fail to show how Codecov identifies failures + expect(2 + 2).toBe(5); + }); + + it('should fail due to missing element', () => { + render(); + // This will fail because the text doesn't exist + expect(screen.getByText('Non-existent button text')).toBeInTheDocument(); + }); + + it('should fail due to incorrect assertion', () => { + const result = Math.random(); + // This will fail because the assertion is wrong + expect(result).toBeGreaterThan(1.0); + }); + + it('should be flaky due to timing issues', async () => { + // This test will fail approximately 30% of the time due to timing + const startTime = Date.now(); + await new Promise(resolve => setTimeout(resolve, Math.random() * 50)); + const endTime = Date.now(); + + // This assertion might fail if timing is too fast + expect(endTime - startTime).toBeGreaterThan(100); + }); + + it('should be flaky due to network simulation', async () => { + // Mock fetch to fail 25% of the time + const mockFetch = jest.fn(); + global.fetch = mockFetch; + + if (Math.random() < 0.25) { + mockFetch.mockRejectedValue(new Error('Simulated network failure')); + } else { + mockFetch.mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ data: 'success' }), + }); + } + + try { + const response = await fetch('/api/test'); + const data = await response.json(); + expect(data.data).toBe('success'); + } catch (error) { + // This will fail when network error occurs + expect(error).toBeUndefined(); + } + }); + + it('should be flaky due to environment dependencies', () => { + // This test will fail if certain conditions aren't met + const currentHour = new Date().getHours(); + // Will fail approximately 50% of the time depending on when tests run + expect(currentHour).toBeLessThan(12); + }); + + it('should fail due to async timeout', async () => { + // This test will fail due to timeout + const promise = new Promise((resolve) => { + setTimeout(resolve, 10000); // 10 second delay + }); + + await expect(promise).resolves.toBeDefined(); + }, 1000); // 1 second timeout + + it('should fail due to exception', () => { + // This test will fail due to an exception + const obj: any = null; + expect(obj.someProperty).toBeDefined(); + }); + + it('should be flaky due to race conditions', async () => { + // Multiple async operations that might race + const promises = Array.from({ length: 5 }, (_, i) => + new Promise(resolve => setTimeout(() => resolve(i), Math.random() * 100)) + ); + + const results = await Promise.all(promises); + + // This might fail due to race conditions + expect(results).toEqual([0, 1, 2, 3, 4]); + }); + + it('should fail due to strict validation', () => { + // Very strict validation that might fail + const email = 'test@example.com'; + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + expect(regex.test(email)).toBe(true); + + // This assertion is too strict and will fail + expect(email).toHaveLength(16); + }); +});