Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,26 @@ jobs:
name: codecov-task-timer
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: npm run test:e2e
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ dist-ssr
*.local
coverage

# Playwright
test-results
playwright-report

# Logs
logs
*.log
Expand Down
58 changes: 58 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ Before committing changes, the following checks **must** pass:
- Catches TypeScript errors and build issues

4. **Tests** - `npm run test -- --run`

- Runs all unit and integration tests
- All tests must pass before committing

5. **E2E Tests** - `npm run test:e2e` (optional for local development)
- Runs end-to-end tests using Playwright
- Tests the full user experience in a real browser
- Automatically run in CI, but can be run locally before major changes

## Automated Pre-Commit Hook

To automatically run these checks before every commit, install the pre-commit hook:
Expand Down Expand Up @@ -70,3 +76,55 @@ To avoid CI failures, make sure all pre-commit checks pass before pushing your c
- Follow ESLint rules (enforced by `npm run lint`)
- Write tests for new features
- Keep code modular and maintainable

## End-to-End Testing

The project uses Playwright for end-to-end testing to ensure the full user experience works correctly.

### Running E2E Tests

```bash
# Run all E2E tests (headless)
npm run test:e2e

# Run E2E tests with UI mode (recommended for development)
npm run test:e2e:ui

# Run E2E tests in headed mode (see the browser)
npm run test:e2e:headed

# Debug E2E tests
npm run test:e2e:debug

# View test report
npm run test:e2e:report
```

### Writing E2E Tests

E2E tests are located in the `e2e/tests/` directory and follow these conventions:

- Use the `TaskTimerPage` page object model from `e2e/page-objects/TaskTimerPage.ts`
- Tests are organized by feature: task management, time tracking, date navigation, and persistence
- Use the test fixtures from `e2e/fixtures/test-fixtures.ts` for automatic setup/teardown
- Helper utilities are available in `e2e/utils/test-helpers.ts`

Example test:

```typescript
import { test, expect } from '../fixtures/test-fixtures';

test('should create a new task', async ({ taskTimerPage }) => {
await taskTimerPage.createTask('My new task');
await taskTimerPage.expectTaskToExist('My new task');
});
```

### E2E Test Coverage

The E2E tests cover:

- Task management (create, edit, delete)
- Time tracking (add/remove time increments)
- Date navigation (switching between dates)
- Data persistence (localStorage and page reloads)
179 changes: 179 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# End-to-End Tests

This directory contains end-to-end tests for the Task Timer application using Playwright.

## Directory Structure

```
e2e/
├── fixtures/ # Test fixtures and setup utilities
│ └── test-fixtures.ts
├── page-objects/ # Page Object Models for UI interactions
│ └── TaskTimerPage.ts
├── tests/ # Test specifications
│ ├── task-management.spec.ts
│ ├── time-tracking.spec.ts
│ ├── date-navigation.spec.ts
│ └── persistence.spec.ts
└── utils/ # Helper utilities
└── test-helpers.ts
```

## Running Tests

All E2E tests use the `/task-timer/` base path to match the production GitHub Pages deployment. This ensures tests accurately reflect the production environment.

```bash
# Run all E2E tests (headless)
npm run test:e2e

# Run E2E tests with UI mode (recommended for development)
npm run test:e2e:ui

# Run E2E tests in headed mode (see the browser)
npm run test:e2e:headed

# Debug E2E tests
npm run test:e2e:debug

# View test report
npm run test:e2e:report
```

Tests run identically in both local and CI environments, building and serving the app with the production configuration.

## Test Organization

### Task Management Tests (`task-management.spec.ts`)

Tests for creating, editing, and deleting tasks:

- Creating tasks with different types
- Editing task descriptions
- Deleting tasks
- Keyboard shortcuts (Enter, Escape)

### Time Tracking Tests (`time-tracking.spec.ts`)

Tests for adding and removing time increments:

- Adding single and multiple time increments
- Removing time increments
- Time calculation across different hours
- Time persistence after task edits

### Date Navigation Tests (`date-navigation.spec.ts`)

Tests for navigating between different dates:

- Moving to previous/next day
- Task isolation by date
- Time data separation by date
- Multi-day workflows

### Persistence Tests (`persistence.spec.ts`)

Tests for data persistence using localStorage:

- Task persistence after reload
- Time entry persistence
- Edit and deletion persistence
- Multi-session workflows
- localStorage data integrity

## Page Object Model

The `TaskTimerPage` class provides a clean API for interacting with the application:

```typescript
// Create a task
await taskTimerPage.createTask('My task');

// Add time to a task
await taskTimerPage.addTimeIncrement('My task', 9, 0); // 9:00-9:15

// Edit a task
await taskTimerPage.editTask('Old name', 'New name');

// Delete a task
await taskTimerPage.deleteTask('My task');

// Navigate dates
await taskTimerPage.goToNextDay();
await taskTimerPage.goToPreviousDay();

// Check task existence
await taskTimerPage.expectTaskToExist('My task');
await taskTimerPage.expectTaskNotToExist('Deleted task');
```

## Test Fixtures

Tests use the `test` fixture from `fixtures/test-fixtures.ts` which:

- Automatically navigates to the application
- Clears localStorage before each test
- Provides a `TaskTimerPage` instance

## Helper Utilities

`utils/test-helpers.ts` provides utility functions:

- `generateTaskName()` - Generate unique task names
- `getTodayTimestamp()` - Get today's date as timestamp
- `createTimeTimestamp()` - Create timestamps for specific times
- `createMockLocalStorageData()` - Create mock localStorage data for testing

## Writing New Tests

When writing new E2E tests:

1. Import the test fixture:

```typescript
import { test, expect } from '../fixtures/test-fixtures';
```

2. Use the page object model:

```typescript
test('my test', async ({ taskTimerPage }) => {
// Use taskTimerPage methods
});
```

3. Generate unique test data:

```typescript
import { generateTaskName } from '../utils/test-helpers';
const taskName = generateTaskName('My task');
```

4. Follow the existing test structure and naming conventions

## CI/CD Integration

E2E tests run automatically in GitHub Actions on every push and pull request. The CI workflow:

- Installs Playwright and Chromium
- Builds the application
- Runs all E2E tests
- Uploads test reports as artifacts

## Debugging

To debug a failing test:

1. Run with UI mode: `npm run test:e2e:ui`
2. Or use debug mode: `npm run test:e2e:debug`
3. Add `await page.pause()` in the test to pause execution
4. Check the Playwright report: `npm run test:e2e:report`

## Best Practices

- Use unique task names with `generateTaskName()` to avoid test interference
- Clean up test data using the built-in fixture (automatic localStorage clear)
- Use descriptive test names that explain what is being tested
- Group related tests in `describe` blocks
- Assert expected outcomes with `expect()` statements
- Keep tests isolated and independent
27 changes: 27 additions & 0 deletions e2e/fixtures/test-fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { test as base } from '@playwright/test';
import { TaskTimerPage } from '../page-objects/TaskTimerPage';

/**
* Extended test fixture that provides a TaskTimerPage instance
* and ensures localStorage is cleared before each test
*/
type TaskTimerFixtures = {
taskTimerPage: TaskTimerPage;
};

export const test = base.extend<TaskTimerFixtures>({
taskTimerPage: async ({ page }, use) => {
const taskTimerPage = new TaskTimerPage(page);

// Navigate to the app and clear localStorage before each test
await taskTimerPage.goto();
await taskTimerPage.clearLocalStorage();
await taskTimerPage.reload();

// Provide the page object to the test
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(taskTimerPage);
},
});

export { expect } from '@playwright/test';
Loading
Loading