remove: broken Playwright E2E testing infrastructure
- Remove playwright.config.ts (references non-existent docker/docker-compose.yaml) - Remove tests/e2e/ directory with broken test files - Remove @playwright/test dependency from package.json - Update documentation to remove E2E test references: - docs/architecture/PROJECT_STRUCTURE.md - docs/implementation/IMPLEMENTATION_SUMMARY.md - README.md - CONTRIBUTING.md - E2E tests were non-functional due to missing Docker setup - Focus on working unit and integration tests instead
This commit is contained in:
+2
-17
@@ -46,7 +46,6 @@ git remote add upstream https://github.com/original-owner/meds.git
|
|||||||
bun install
|
bun install
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Edit .env with your development values
|
# Edit .env with your development values
|
||||||
docker compose -f docker/docker-compose.yaml up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3. Create Feature Branch
|
#### 3. Create Feature Branch
|
||||||
@@ -313,22 +312,13 @@ bun test --coverage
|
|||||||
# Run integration tests
|
# Run integration tests
|
||||||
bun run test:integration
|
bun run test:integration
|
||||||
|
|
||||||
# Run E2E tests with Playwright
|
# Run all tests (unit + integration)
|
||||||
bun run test:e2e
|
|
||||||
|
|
||||||
# Run E2E tests in UI mode
|
|
||||||
bun run test:e2e:ui
|
|
||||||
|
|
||||||
# Debug E2E tests
|
|
||||||
bun run test:e2e:debug
|
|
||||||
|
|
||||||
# Run all tests (unit + integration + e2e)
|
|
||||||
bun run test:all
|
bun run test:all
|
||||||
```
|
```
|
||||||
|
|
||||||
### E2E Testing
|
### E2E Testing
|
||||||
|
|
||||||
E2E tests use Playwright and are located in `tests/e2e/`. When adding new features:
|
Integration tests are located in `tests/integration/`. When adding new features:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Use custom fixtures for authenticated testing
|
// Use custom fixtures for authenticated testing
|
||||||
@@ -442,7 +432,6 @@ We follow [Semantic Versioning](https://semver.org/):
|
|||||||
### Required Tools
|
### Required Tools
|
||||||
|
|
||||||
- **Node.js 18+** or **Bun 1.0+**
|
- **Node.js 18+** or **Bun 1.0+**
|
||||||
- **Docker and Docker Compose**
|
|
||||||
- **Git**
|
- **Git**
|
||||||
- **Code Editor** (VS Code recommended)
|
- **Code Editor** (VS Code recommended)
|
||||||
|
|
||||||
@@ -473,10 +462,6 @@ bun test # Run tests
|
|||||||
bun test:coverage # Run tests with coverage
|
bun test:coverage # Run tests with coverage
|
||||||
bun test:watch # Run tests in watch mode
|
bun test:watch # Run tests in watch mode
|
||||||
|
|
||||||
# Docker
|
|
||||||
docker compose -f docker/docker-compose.yaml up -d # Start services
|
|
||||||
docker compose -f docker/docker-compose.yaml logs # View logs
|
|
||||||
docker compose -f docker/docker-compose.yaml down # Stop services
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🆘 Getting Help
|
## 🆘 Getting Help
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ make dev
|
|||||||
|
|
||||||
### **Available Commands**
|
### **Available Commands**
|
||||||
|
|
||||||
```bash
|
````bash
|
||||||
# Development
|
# Development
|
||||||
bun run dev # Start development server
|
bun run dev # Start development server
|
||||||
bun run build # Build for production
|
bun run build # Build for production
|
||||||
@@ -135,10 +135,10 @@ bun run lint:fix # Fix linting issues
|
|||||||
bun run type-check # TypeScript type checking
|
bun run type-check # TypeScript type checking
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
```bash
|
||||||
bun run test # Run unit tests
|
bun run test # Run unit tests
|
||||||
bun run test:watch # Run tests in watch mode
|
bun run test:watch # Run tests in watch mode
|
||||||
bun run test:e2e # Run end-to-end tests
|
````
|
||||||
```
|
|
||||||
|
|
||||||
### **Makefile Commands**
|
### **Makefile Commands**
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ bun run type-check # TypeScript validation
|
|||||||
- **Unit Tests**: Jest-based tests for services and utilities
|
- **Unit Tests**: Jest-based tests for services and utilities
|
||||||
- **Component Tests**: React component testing
|
- **Component Tests**: React component testing
|
||||||
- **Integration Tests**: Service integration validation
|
- **Integration Tests**: Service integration validation
|
||||||
- **E2E Tests**: Playwright-based full user journey testing
|
- **Integration Tests**: Service validation and API testing
|
||||||
|
|
||||||
### **Running Tests**
|
### **Running Tests**
|
||||||
|
|
||||||
@@ -267,11 +267,8 @@ bun run test
|
|||||||
# Run with coverage
|
# Run with coverage
|
||||||
bun run test:coverage
|
bun run test:coverage
|
||||||
|
|
||||||
# Run E2E tests
|
# Run integration tests
|
||||||
bun run test:e2e
|
bun run test:services
|
||||||
|
|
||||||
# Run E2E tests in UI mode
|
|
||||||
bun run test:e2e:ui
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
"@babel/core": "^7.28.4",
|
"@babel/core": "^7.28.4",
|
||||||
"@babel/preset-env": "^7.28.3",
|
"@babel/preset-env": "^7.28.3",
|
||||||
"@babel/preset-typescript": "^7.27.1",
|
"@babel/preset-typescript": "^7.27.1",
|
||||||
"@playwright/test": "^1.55.0",
|
|
||||||
"@secretlint/node": "^11.2.3",
|
"@secretlint/node": "^11.2.3",
|
||||||
"@secretlint/secretlint-rule-preset-recommend": "^11.2.3",
|
"@secretlint/secretlint-rule-preset-recommend": "^11.2.3",
|
||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
@@ -432,8 +431,6 @@
|
|||||||
|
|
||||||
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
|
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
|
||||||
|
|
||||||
"@playwright/test": ["@playwright/test@1.55.0", "", { "dependencies": { "playwright": "1.55.0" }, "bin": { "playwright": "cli.js" } }, "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.0", "", { "os": "android", "cpu": "arm" }, "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ=="],
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.0", "", { "os": "android", "cpu": "arm" }, "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.0", "", { "os": "android", "cpu": "arm64" }, "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw=="],
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.0", "", { "os": "android", "cpu": "arm64" }, "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw=="],
|
||||||
@@ -1570,10 +1567,6 @@
|
|||||||
|
|
||||||
"pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
|
"pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
|
||||||
|
|
||||||
"playwright": ["playwright@1.55.0", "", { "dependencies": { "playwright-core": "1.55.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA=="],
|
|
||||||
|
|
||||||
"playwright-core": ["playwright-core@1.55.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg=="],
|
|
||||||
|
|
||||||
"plugin-error": ["plugin-error@1.0.1", "", { "dependencies": { "ansi-colors": "^1.0.1", "arr-diff": "^4.0.0", "arr-union": "^3.1.0", "extend-shallow": "^3.0.2" } }, "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA=="],
|
"plugin-error": ["plugin-error@1.0.1", "", { "dependencies": { "ansi-colors": "^1.0.1", "arr-diff": "^4.0.0", "arr-union": "^3.1.0", "extend-shallow": "^3.0.2" } }, "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA=="],
|
||||||
|
|
||||||
"pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="],
|
"pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="],
|
||||||
@@ -2134,8 +2127,6 @@
|
|||||||
|
|
||||||
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||||
|
|
||||||
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
|
||||||
|
|
||||||
"raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
|
"raw-body/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
|
||||||
|
|
||||||
"rc-config-loader/js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
"rc-config-loader/js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||||
|
|||||||
@@ -110,10 +110,6 @@ rxminder/
|
|||||||
│ │ ├── 🔧 admin-login-debug.js # Admin debugging
|
│ │ ├── 🔧 admin-login-debug.js # Admin debugging
|
||||||
│ │ ├── 🔧 auth-db-debug.js # Auth debugging
|
│ │ ├── 🔧 auth-db-debug.js # Auth debugging
|
||||||
│ │ └── 🔧 debug-email-validation.js # Email debugging
|
│ │ └── 🔧 debug-email-validation.js # Email debugging
|
||||||
│ └── 📁 e2e/ # End-to-end tests with Playwright
|
|
||||||
│ ├── 📝 README.md # E2E testing documentation
|
|
||||||
│ ├── 🧪 fixtures.ts # Custom test fixtures
|
|
||||||
│ ├── 🧪 helpers.ts # Test utilities and data
|
|
||||||
│ ├── 🧪 auth.spec.ts # Authentication flow tests
|
│ ├── 🧪 auth.spec.ts # Authentication flow tests
|
||||||
│ ├── 🧪 medication.spec.ts # Medication management tests
|
│ ├── 🧪 medication.spec.ts # Medication management tests
|
||||||
│ ├── 🧪 admin.spec.ts # Admin interface tests
|
│ ├── 🧪 admin.spec.ts # Admin interface tests
|
||||||
@@ -149,7 +145,6 @@ rxminder/
|
|||||||
├── 🎨 .prettierrc # Code formatting rules
|
├── 🎨 .prettierrc # Code formatting rules
|
||||||
├── ⚡ eslint.config.cjs # Linting configuration
|
├── ⚡ eslint.config.cjs # Linting configuration
|
||||||
├── 🧪 jest.config.json # Test configuration
|
├── 🧪 jest.config.json # Test configuration
|
||||||
├── 🎭 playwright.config.ts # E2E test configuration
|
|
||||||
├── 📝 .markdownlint.json # Markdown linting
|
├── 📝 .markdownlint.json # Markdown linting
|
||||||
├── 🔒 .secretlintrc.json # Secret detection
|
├── 🔒 .secretlintrc.json # Secret detection
|
||||||
├── ✏️ .editorconfig # Editor consistency
|
├── ✏️ .editorconfig # Editor consistency
|
||||||
@@ -224,8 +219,7 @@ Comprehensive testing at multiple levels:
|
|||||||
```
|
```
|
||||||
tests/
|
tests/
|
||||||
├── integration/ # Service integration tests
|
├── integration/ # Service integration tests
|
||||||
├── manual/ # Debug scripts and manual testing
|
└── manual/ # Debug scripts and manual testing
|
||||||
└── e2e/ # Full application testing
|
|
||||||
|
|
||||||
# Unit tests alongside code
|
# Unit tests alongside code
|
||||||
services/auth/__tests__/ # Co-located with services
|
services/auth/__tests__/ # Co-located with services
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ bun run dev
|
|||||||
- Unit tests for services and utilities
|
- Unit tests for services and utilities
|
||||||
- Component tests for React components
|
- Component tests for React components
|
||||||
- Integration tests for API endpoints
|
- Integration tests for API endpoints
|
||||||
- End-to-end tests with Playwright
|
- Integration tests for service validation
|
||||||
|
|
||||||
### Test Environment
|
### Test Environment
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@
|
|||||||
"@babel/core": "^7.28.4",
|
"@babel/core": "^7.28.4",
|
||||||
"@babel/preset-env": "^7.28.3",
|
"@babel/preset-env": "^7.28.3",
|
||||||
"@babel/preset-typescript": "^7.27.1",
|
"@babel/preset-typescript": "^7.27.1",
|
||||||
"@playwright/test": "^1.55.0",
|
|
||||||
"@secretlint/node": "^11.2.3",
|
"@secretlint/node": "^11.2.3",
|
||||||
"@secretlint/secretlint-rule-preset-recommend": "^11.2.3",
|
"@secretlint/secretlint-rule-preset-recommend": "^11.2.3",
|
||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://playwright.dev/docs/test-configuration
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: './tests/e2e',
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Retry on CI only */
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
|
||||||
workers: process.env.CI ? 1 : undefined,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: 'html',
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
||||||
baseURL: 'http://localhost:8080',
|
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: 'on-first-retry',
|
|
||||||
|
|
||||||
/* Take screenshot on failure */
|
|
||||||
screenshot: 'only-on-failure',
|
|
||||||
|
|
||||||
/* Record video on failure */
|
|
||||||
video: 'retain-on-failure',
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: 'chromium',
|
|
||||||
use: { ...devices['Desktop Chrome'] },
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'firefox',
|
|
||||||
use: { ...devices['Desktop Firefox'] },
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'webkit',
|
|
||||||
use: { ...devices['Desktop Safari'] },
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
|
||||||
{
|
|
||||||
name: 'Mobile Chrome',
|
|
||||||
use: { ...devices['Pixel 5'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Mobile Safari',
|
|
||||||
use: { ...devices['iPhone 12'] },
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
|
||||||
// {
|
|
||||||
// name: 'Microsoft Edge',
|
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Google Chrome',
|
|
||||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
webServer: {
|
|
||||||
command: 'docker compose -f docker/docker-compose.yaml up -d',
|
|
||||||
url: 'http://localhost:8080',
|
|
||||||
reuseExistingServer: !process.env.CI,
|
|
||||||
timeout: 120 * 1000, // 2 minutes
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
# 🎭 End-to-End Testing with Playwright
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This directory contains comprehensive end-to-end tests for the Medication Reminder App using Playwright. These tests simulate real user interactions across different browsers and devices.
|
|
||||||
|
|
||||||
## Test Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/e2e/
|
|
||||||
├── README.md # This documentation
|
|
||||||
├── fixtures.ts # Custom test fixtures and utilities
|
|
||||||
├── helpers.ts # Test helper functions and data
|
|
||||||
├── auth.spec.ts # Authentication flow tests
|
|
||||||
├── medication.spec.ts # Medication management tests
|
|
||||||
├── admin.spec.ts # Admin interface tests
|
|
||||||
├── ui-navigation.spec.ts # UI and navigation tests
|
|
||||||
└── reminders.spec.ts # Reminder system tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Categories
|
|
||||||
|
|
||||||
### 🔐 **Authentication Tests** (`auth.spec.ts`)
|
|
||||||
|
|
||||||
- User registration and login
|
|
||||||
- Admin authentication
|
|
||||||
- OAuth button visibility
|
|
||||||
- Invalid credential handling
|
|
||||||
- Session management
|
|
||||||
|
|
||||||
### 💊 **Medication Management** (`medication.spec.ts`)
|
|
||||||
|
|
||||||
- Adding new medications
|
|
||||||
- Editing existing medications
|
|
||||||
- Deleting medications
|
|
||||||
- Marking doses as taken
|
|
||||||
- Viewing medication history
|
|
||||||
|
|
||||||
### 👑 **Admin Interface** (`admin.spec.ts`)
|
|
||||||
|
|
||||||
- User management operations
|
|
||||||
- Password changes
|
|
||||||
- User status toggles
|
|
||||||
- Admin permissions
|
|
||||||
|
|
||||||
### 🎨 **UI & Navigation** (`ui-navigation.spec.ts`)
|
|
||||||
|
|
||||||
- Theme switching
|
|
||||||
- Modal interactions
|
|
||||||
- Responsive design
|
|
||||||
- Search functionality
|
|
||||||
- Account management
|
|
||||||
|
|
||||||
### ⏰ **Reminder System** (`reminders.spec.ts`)
|
|
||||||
|
|
||||||
- Custom reminder creation
|
|
||||||
- Reminder editing and deletion
|
|
||||||
- Scheduled dose display
|
|
||||||
- Missed dose handling
|
|
||||||
|
|
||||||
## Setup and Installation
|
|
||||||
|
|
||||||
### 1. Install Playwright
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install Playwright and browsers
|
|
||||||
bun add -D @playwright/test
|
|
||||||
bunx playwright install
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Update Package.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
"test:e2e": "playwright test",
|
|
||||||
"test:e2e:ui": "playwright test --ui",
|
|
||||||
"test:e2e:debug": "playwright test --debug",
|
|
||||||
"test:e2e:report": "playwright show-report"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running Tests
|
|
||||||
|
|
||||||
### Basic Test Execution
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run all E2E tests
|
|
||||||
bun run test:e2e
|
|
||||||
|
|
||||||
# Run tests in UI mode (interactive)
|
|
||||||
bun run test:e2e:ui
|
|
||||||
|
|
||||||
# Run specific test file
|
|
||||||
bunx playwright test auth.spec.ts
|
|
||||||
|
|
||||||
# Run tests in debug mode
|
|
||||||
bun run test:e2e:debug
|
|
||||||
```
|
|
||||||
|
|
||||||
### Browser-Specific Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run on specific browser
|
|
||||||
bunx playwright test --project=chromium
|
|
||||||
bunx playwright test --project=firefox
|
|
||||||
bunx playwright test --project=webkit
|
|
||||||
|
|
||||||
# Run on mobile browsers
|
|
||||||
bunx playwright test --project="Mobile Chrome"
|
|
||||||
bunx playwright test --project="Mobile Safari"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Reporting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Generate and view HTML report
|
|
||||||
bun run test:e2e:report
|
|
||||||
|
|
||||||
# Run with specific reporter
|
|
||||||
bunx playwright test --reporter=line
|
|
||||||
bunx playwright test --reporter=json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Configuration
|
|
||||||
|
|
||||||
The tests are configured via `playwright.config.ts`:
|
|
||||||
|
|
||||||
- **Base URL**: `http://localhost:8080`
|
|
||||||
- **Auto-start**: Docker Compose before tests
|
|
||||||
- **Browsers**: Chrome, Firefox, Safari, Mobile Chrome, Mobile Safari
|
|
||||||
- **Retries**: 2 on CI, 0 locally
|
|
||||||
- **Screenshots**: On failure
|
|
||||||
- **Videos**: On failure
|
|
||||||
- **Traces**: On retry
|
|
||||||
|
|
||||||
## Test Data and Fixtures
|
|
||||||
|
|
||||||
### Custom Fixtures (`fixtures.ts`)
|
|
||||||
|
|
||||||
- `adminPage`: Auto-login as admin user
|
|
||||||
- `userPage`: Auto-login as regular user
|
|
||||||
|
|
||||||
### Helper Functions (`helpers.ts`)
|
|
||||||
|
|
||||||
- `MedicationHelpers`: Medication CRUD operations
|
|
||||||
- `AuthHelpers`: Authentication actions
|
|
||||||
- `ModalHelpers`: Modal interactions
|
|
||||||
- `WaitHelpers`: Wait utilities
|
|
||||||
- `TestData`: Pre-defined test data
|
|
||||||
|
|
||||||
### Example Usage
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { test } from './fixtures';
|
|
||||||
import { MedicationHelpers, TestData } from './helpers';
|
|
||||||
|
|
||||||
test('should add medication', async ({ adminPage }) => {
|
|
||||||
const medicationHelper = new MedicationHelpers(adminPage);
|
|
||||||
const testMed = TestData.medications[0];
|
|
||||||
|
|
||||||
await medicationHelper.addMedication(testMed.name, testMed.dosage, testMed.frequency);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### ✅ Test Organization
|
|
||||||
|
|
||||||
- Group related tests in describe blocks
|
|
||||||
- Use descriptive test names
|
|
||||||
- Keep tests independent and isolated
|
|
||||||
|
|
||||||
### ✅ Selectors
|
|
||||||
|
|
||||||
- Use data-testid attributes for reliable targeting
|
|
||||||
- Prefer semantic selectors (role, text content)
|
|
||||||
- Avoid CSS selectors that may change
|
|
||||||
|
|
||||||
### ✅ Waiting Strategies
|
|
||||||
|
|
||||||
- Use `waitForSelector()` for dynamic content
|
|
||||||
- Leverage auto-waiting for most actions
|
|
||||||
- Add explicit waits for complex interactions
|
|
||||||
|
|
||||||
### ✅ Test Data
|
|
||||||
|
|
||||||
- Use helper functions for common operations
|
|
||||||
- Keep test data in centralized location
|
|
||||||
- Clean up test data after tests
|
|
||||||
|
|
||||||
## Debugging Tests
|
|
||||||
|
|
||||||
### Local Debugging
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Debug specific test
|
|
||||||
bunx playwright test auth.spec.ts --debug
|
|
||||||
|
|
||||||
# Run with headed browser
|
|
||||||
bunx playwright test --headed
|
|
||||||
|
|
||||||
# Slow down execution
|
|
||||||
bunx playwright test --slow-mo=1000
|
|
||||||
```
|
|
||||||
|
|
||||||
### CI/CD Integration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run in CI mode
|
|
||||||
CI=true bunx playwright test
|
|
||||||
|
|
||||||
# Generate artifacts for CI
|
|
||||||
bunx playwright test --reporter=github
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding New Tests
|
|
||||||
|
|
||||||
### 1. Create Test File
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { test, expect } from './fixtures';
|
|
||||||
|
|
||||||
test.describe('New Feature', () => {
|
|
||||||
test('should do something', async ({ adminPage }) => {
|
|
||||||
// Test implementation
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Add Helper Functions
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// In helpers.ts
|
|
||||||
export class NewFeatureHelpers {
|
|
||||||
constructor(private page: any) {}
|
|
||||||
|
|
||||||
async performAction() {
|
|
||||||
// Helper implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Update Documentation
|
|
||||||
|
|
||||||
- Add test description to this README
|
|
||||||
- Update test count in project documentation
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**Tests timeout:**
|
|
||||||
|
|
||||||
- Increase timeout in config
|
|
||||||
- Add explicit waits
|
|
||||||
- Check application startup time
|
|
||||||
|
|
||||||
**Flaky tests:**
|
|
||||||
|
|
||||||
- Add proper wait conditions
|
|
||||||
- Use retry logic
|
|
||||||
- Check for race conditions
|
|
||||||
|
|
||||||
**Browser compatibility:**
|
|
||||||
|
|
||||||
- Test across all configured browsers
|
|
||||||
- Check for browser-specific issues
|
|
||||||
- Use cross-browser compatible selectors
|
|
||||||
|
|
||||||
### Debug Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Show browser developer tools
|
|
||||||
bunx playwright test --debug
|
|
||||||
|
|
||||||
# Record test execution
|
|
||||||
bunx playwright codegen localhost:8080
|
|
||||||
|
|
||||||
# Trace viewer
|
|
||||||
bunx playwright show-trace trace.zip
|
|
||||||
```
|
|
||||||
|
|
||||||
## Continuous Integration
|
|
||||||
|
|
||||||
Example GitHub Actions workflow:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
name: E2E Tests
|
|
||||||
on: [push, pull_request]
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
- run: bun install
|
|
||||||
- run: bunx playwright install --with-deps
|
|
||||||
- run: bun run test:e2e
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: playwright-report
|
|
||||||
path: playwright-report/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Coverage and Metrics
|
|
||||||
|
|
||||||
The E2E tests provide coverage for:
|
|
||||||
|
|
||||||
- ✅ User authentication flows
|
|
||||||
- ✅ Core medication management
|
|
||||||
- ✅ Admin functionality
|
|
||||||
- ✅ UI interactions and navigation
|
|
||||||
- ✅ Responsive design
|
|
||||||
- ✅ Cross-browser compatibility
|
|
||||||
|
|
||||||
For optimal coverage, run tests regularly and add new tests for new features.
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
test.describe('Admin Interface', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
// Login as admin
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await page.fill('input[type="password"]', 'admin123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Wait for main app and open admin interface
|
|
||||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
|
||||||
await page.click('button:has-text("Admin")');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display admin interface', async ({ page }) => {
|
|
||||||
await expect(page.locator('text=Admin Interface')).toBeVisible();
|
|
||||||
await expect(page.locator('text=User Management')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show list of users', async ({ page }) => {
|
|
||||||
// Should show admin user at minimum
|
|
||||||
await expect(page.locator('text=admin@localhost')).toBeVisible();
|
|
||||||
await expect(page.locator('text=Admin')).toBeVisible(); // Role
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should allow changing user password', async ({ page }) => {
|
|
||||||
// Click on a user's change password button
|
|
||||||
await page.click('[data-testid="change-password"]');
|
|
||||||
|
|
||||||
// Fill new password
|
|
||||||
await page.fill('input[type="password"]', 'NewPassword123!');
|
|
||||||
|
|
||||||
// Submit password change
|
|
||||||
await page.click('button:has-text("Change Password")');
|
|
||||||
|
|
||||||
// Should show success message
|
|
||||||
await expect(page.locator('text=Password changed')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should allow suspending/activating users', async ({ page }) => {
|
|
||||||
// Look for user status controls
|
|
||||||
const statusButton = page
|
|
||||||
.locator('[data-testid="toggle-user-status"]')
|
|
||||||
.first();
|
|
||||||
await expect(statusButton).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should refresh user list', async ({ page }) => {
|
|
||||||
await page.click('button:has-text("Refresh")');
|
|
||||||
|
|
||||||
// Should still show users after refresh
|
|
||||||
await expect(page.locator('text=admin@localhost')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should close admin interface', async ({ page }) => {
|
|
||||||
await page.click('button[aria-label="Close"]');
|
|
||||||
|
|
||||||
// Should return to main app
|
|
||||||
await expect(page.locator('text=Admin Interface')).not.toBeVisible();
|
|
||||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
test.describe('Authentication Flow', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto('/');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display login page for unauthenticated users', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await expect(page.locator('h2')).toContainText(['Sign In', 'Login']);
|
|
||||||
await expect(page.locator('input[type="email"]')).toBeVisible();
|
|
||||||
await expect(page.locator('input[type="password"]')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should allow user registration', async ({ page }) => {
|
|
||||||
// Click register tab/link
|
|
||||||
await page.click('text=Register');
|
|
||||||
|
|
||||||
// Fill registration form
|
|
||||||
await page.fill('input[type="email"]', 'test@example.com');
|
|
||||||
await page.fill('input[name="username"]', 'testuser');
|
|
||||||
await page.fill('input[type="password"]', 'TestPassword123!');
|
|
||||||
|
|
||||||
// Submit registration
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Should show verification message or redirect
|
|
||||||
await expect(page.locator('text=verification')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should login with admin credentials', async ({ page }) => {
|
|
||||||
// Fill login form with admin credentials
|
|
||||||
await page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await page.fill('input[type="password"]', 'admin123!');
|
|
||||||
|
|
||||||
// Submit login
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Should redirect to main app
|
|
||||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
|
||||||
await expect(page.locator('text=Admin')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show error for invalid credentials', async ({ page }) => {
|
|
||||||
await page.fill('input[type="email"]', 'invalid@example.com');
|
|
||||||
await page.fill('input[type="password"]', 'wrongpassword');
|
|
||||||
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
await expect(page.locator('text=Invalid')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle OAuth login buttons', async ({ page }) => {
|
|
||||||
await expect(page.locator('button:has-text("Google")')).toBeVisible();
|
|
||||||
await expect(page.locator('button:has-text("GitHub")')).toBeVisible();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { test as base, Page } from '@playwright/test';
|
|
||||||
|
|
||||||
// Define fixture types
|
|
||||||
type TestFixtures = {
|
|
||||||
adminPage: Page;
|
|
||||||
userPage: Page;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extend basic test with custom fixtures
|
|
||||||
export const test = base.extend<TestFixtures>({
|
|
||||||
// Auto-login fixture for admin user
|
|
||||||
adminPage: async ({ page }, use) => {
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await page.fill('input[type="password"]', 'admin123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Wait for app to load
|
|
||||||
await page.waitForSelector('h1:has-text("Medication Reminder")');
|
|
||||||
|
|
||||||
await use(page);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Regular user login fixture
|
|
||||||
userPage: async ({ page }, use) => {
|
|
||||||
await page.goto('/');
|
|
||||||
|
|
||||||
// Register a test user first if needed
|
|
||||||
await page.click('text=Register');
|
|
||||||
await page.fill('input[type="email"]', 'testuser@example.com');
|
|
||||||
await page.fill('input[name="username"]', 'testuser');
|
|
||||||
await page.fill('input[type="password"]', 'TestPassword123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// For mock database, user might be auto-verified
|
|
||||||
// Wait for either verification message or app load
|
|
||||||
try {
|
|
||||||
await page.waitForSelector('h1:has-text("Medication Reminder")', {
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// If not auto-logged in, login manually
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', 'testuser@example.com');
|
|
||||||
await page.fill('input[type="password"]', 'TestPassword123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
await page.waitForSelector('h1:has-text("Medication Reminder")');
|
|
||||||
}
|
|
||||||
|
|
||||||
await use(page);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { expect } from '@playwright/test';
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
// E2E Test Utilities and Helpers
|
|
||||||
import { Page } from '@playwright/test';
|
|
||||||
|
|
||||||
// Type definitions for better type safety
|
|
||||||
interface MedicationData {
|
|
||||||
name: string;
|
|
||||||
dosage: string;
|
|
||||||
frequency: string;
|
|
||||||
times: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserData {
|
|
||||||
email: string;
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ReminderData {
|
|
||||||
title: string;
|
|
||||||
icon: string;
|
|
||||||
frequency: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MedicationHelpers {
|
|
||||||
constructor(private page: Page) {}
|
|
||||||
|
|
||||||
async addMedication(
|
|
||||||
name: string,
|
|
||||||
dosage: string,
|
|
||||||
frequency: string = 'daily',
|
|
||||||
times: string = '1'
|
|
||||||
): Promise<void> {
|
|
||||||
await this.page.click('button:has-text("Add Medication")');
|
|
||||||
await this.page.fill('input[name="name"]', name);
|
|
||||||
await this.page.fill('input[name="dosage"]', dosage);
|
|
||||||
await this.page.selectOption('select[name="frequency"]', frequency);
|
|
||||||
if (times !== '1') {
|
|
||||||
await this.page.fill('input[name="times"]', times);
|
|
||||||
}
|
|
||||||
await this.page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Wait for medication to appear
|
|
||||||
await this.page.waitForSelector(`text=${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteMedication(name: string): Promise<void> {
|
|
||||||
await this.page.click('button:has-text("Manage")');
|
|
||||||
|
|
||||||
// Find the medication row and click delete
|
|
||||||
await this.page
|
|
||||||
.locator(`tr:has-text("${name}") [data-testid="delete-medication"]`)
|
|
||||||
.click();
|
|
||||||
await this.page.click('button:has-text("Delete")');
|
|
||||||
|
|
||||||
// Close manage modal
|
|
||||||
await this.page.click('button:has-text("Close")');
|
|
||||||
}
|
|
||||||
|
|
||||||
async takeDose(medicationName: string): Promise<void> {
|
|
||||||
await this.page
|
|
||||||
.locator(
|
|
||||||
`.dose-card:has-text("${medicationName}") button:has-text("Take")`
|
|
||||||
)
|
|
||||||
.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthHelpers {
|
|
||||||
constructor(private page: Page) {}
|
|
||||||
|
|
||||||
async loginAsAdmin(): Promise<void> {
|
|
||||||
await this.page.goto('/');
|
|
||||||
await this.page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await this.page.fill('input[type="password"]', 'admin123!');
|
|
||||||
await this.page.click('button[type="submit"]');
|
|
||||||
await this.page.waitForSelector('h1:has-text("Medication Reminder")');
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerUser(
|
|
||||||
email: string,
|
|
||||||
username: string,
|
|
||||||
password: string
|
|
||||||
): Promise<void> {
|
|
||||||
await this.page.goto('/');
|
|
||||||
await this.page.click('text=Register');
|
|
||||||
await this.page.fill('input[type="email"]', email);
|
|
||||||
await this.page.fill('input[name="username"]', username);
|
|
||||||
await this.page.fill('input[type="password"]', password);
|
|
||||||
await this.page.click('button[type="submit"]');
|
|
||||||
}
|
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
|
||||||
await this.page.click('[data-testid="avatar-dropdown"]');
|
|
||||||
await this.page.click('button:has-text("Logout")');
|
|
||||||
await this.page.waitForSelector('h2:has-text("Sign In")');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModalHelpers {
|
|
||||||
constructor(private page: Page) {}
|
|
||||||
|
|
||||||
async openModal(buttonText: string): Promise<void> {
|
|
||||||
await this.page.click(`button:has-text("${buttonText}")`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async closeModal(): Promise<void> {
|
|
||||||
await this.page.click('button:has-text("Close")');
|
|
||||||
}
|
|
||||||
|
|
||||||
async confirmAction(): Promise<void> {
|
|
||||||
await this.page.click('button:has-text("Confirm")');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WaitHelpers {
|
|
||||||
constructor(private page: Page) {}
|
|
||||||
|
|
||||||
async waitForAppLoad(): Promise<void> {
|
|
||||||
await this.page.waitForSelector('h1:has-text("Medication Reminder")');
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForModal(title: string): Promise<void> {
|
|
||||||
await this.page.waitForSelector(`text=${title}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForNotification(message: string): Promise<void> {
|
|
||||||
await this.page.waitForSelector(`text=${message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data generators for testing
|
|
||||||
export const TestData = {
|
|
||||||
medications: [
|
|
||||||
{ name: 'Aspirin', dosage: '100mg', frequency: 'daily', times: '1' },
|
|
||||||
{ name: 'Vitamin D', dosage: '1000 IU', frequency: 'daily', times: '1' },
|
|
||||||
{ name: 'Omega-3', dosage: '500mg', frequency: 'daily', times: '2' },
|
|
||||||
{ name: 'Calcium', dosage: '600mg', frequency: 'twice_daily', times: '1' },
|
|
||||||
] as const satisfies readonly MedicationData[],
|
|
||||||
|
|
||||||
users: [
|
|
||||||
{
|
|
||||||
email: 'test1@example.com',
|
|
||||||
username: 'testuser1',
|
|
||||||
password: 'TestPass123!',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: 'test2@example.com',
|
|
||||||
username: 'testuser2',
|
|
||||||
password: 'TestPass456!',
|
|
||||||
},
|
|
||||||
] as const satisfies readonly UserData[],
|
|
||||||
|
|
||||||
reminders: [
|
|
||||||
{ title: 'Drink Water', icon: 'bell', frequency: 60 },
|
|
||||||
{ title: 'Exercise', icon: 'heart', frequency: 1440 }, // Daily
|
|
||||||
{ title: 'Check Blood Pressure', icon: 'chart', frequency: 10080 }, // Weekly
|
|
||||||
] as const satisfies readonly ReminderData[],
|
|
||||||
} as const;
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
test.describe('Medication Management', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
// Login as admin first
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await page.fill('input[type="password"]', 'admin123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Wait for main app to load
|
|
||||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should add a new medication', async ({ page }) => {
|
|
||||||
// Click add medication button
|
|
||||||
await page.click('button:has-text("Add Medication")');
|
|
||||||
|
|
||||||
// Fill medication form
|
|
||||||
await page.fill('input[name="name"]', 'Aspirin');
|
|
||||||
await page.fill('input[name="dosage"]', '100mg');
|
|
||||||
await page.selectOption('select[name="frequency"]', 'daily');
|
|
||||||
await page.fill('input[name="times"]', '2');
|
|
||||||
|
|
||||||
// Submit form
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Should see medication in list
|
|
||||||
await expect(page.locator('text=Aspirin')).toBeVisible();
|
|
||||||
await expect(page.locator('text=100mg')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should edit existing medication', async ({ page }) => {
|
|
||||||
// First add a medication
|
|
||||||
await page.click('button:has-text("Add Medication")');
|
|
||||||
await page.fill('input[name="name"]', 'Vitamin D');
|
|
||||||
await page.fill('input[name="dosage"]', '1000 IU');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Click edit button for the medication
|
|
||||||
await page.click('[data-testid="edit-medication"]');
|
|
||||||
|
|
||||||
// Update dosage
|
|
||||||
await page.fill('input[name="dosage"]', '2000 IU');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Should see updated dosage
|
|
||||||
await expect(page.locator('text=2000 IU')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should delete medication', async ({ page }) => {
|
|
||||||
// Add a medication first
|
|
||||||
await page.click('button:has-text("Add Medication")');
|
|
||||||
await page.fill('input[name="name"]', 'Test Medicine');
|
|
||||||
await page.fill('input[name="dosage"]', '50mg');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Open manage medications modal
|
|
||||||
await page.click('button:has-text("Manage")');
|
|
||||||
|
|
||||||
// Delete the medication
|
|
||||||
await page.click('[data-testid="delete-medication"]');
|
|
||||||
await page.click('button:has-text("Delete")'); // Confirm deletion
|
|
||||||
|
|
||||||
// Should not see medication anymore
|
|
||||||
await expect(page.locator('text=Test Medicine')).not.toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should mark dose as taken', async ({ page }) => {
|
|
||||||
// Add a medication first
|
|
||||||
await page.click('button:has-text("Add Medication")');
|
|
||||||
await page.fill('input[name="name"]', 'Daily Vitamin');
|
|
||||||
await page.fill('input[name="dosage"]', '1 tablet');
|
|
||||||
await page.selectOption('select[name="frequency"]', 'daily');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Find and click the "Take" button for upcoming dose
|
|
||||||
await page.click('button:has-text("Take")');
|
|
||||||
|
|
||||||
// Should show as taken
|
|
||||||
await expect(page.locator('text=Taken')).toBeVisible();
|
|
||||||
await expect(page.locator('.bg-green-50')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show medication history', async ({ page }) => {
|
|
||||||
// Open history modal
|
|
||||||
await page.click('button:has-text("History")');
|
|
||||||
|
|
||||||
// Should show history modal
|
|
||||||
await expect(page.locator('text=Medication History')).toBeVisible();
|
|
||||||
|
|
||||||
// Close modal
|
|
||||||
await page.click('button:has-text("Close")');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
test.describe('Reminder System', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
// Login as admin
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await page.fill('input[type="password"]', 'admin123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create custom reminder', async ({ page }) => {
|
|
||||||
// Click manage reminders
|
|
||||||
await page.click('button:has-text("Reminders")');
|
|
||||||
|
|
||||||
// Add new reminder
|
|
||||||
await page.click('button:has-text("Add Reminder")');
|
|
||||||
|
|
||||||
// Fill reminder form
|
|
||||||
await page.fill('input[name="title"]', 'Drink Water');
|
|
||||||
await page.selectOption('select[name="icon"]', 'bell');
|
|
||||||
await page.fill('input[name="frequency"]', '60'); // Every hour
|
|
||||||
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Should see reminder in list
|
|
||||||
await expect(page.locator('text=Drink Water')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should edit custom reminder', async ({ page }) => {
|
|
||||||
// First create a reminder
|
|
||||||
await page.click('button:has-text("Reminders")');
|
|
||||||
await page.click('button:has-text("Add Reminder")');
|
|
||||||
await page.fill('input[name="title"]', 'Exercise');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Edit the reminder
|
|
||||||
await page.click('[data-testid="edit-reminder"]');
|
|
||||||
await page.fill('input[name="title"]', 'Morning Exercise');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
await expect(page.locator('text=Morning Exercise')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should delete custom reminder', async ({ page }) => {
|
|
||||||
// Create and then delete reminder
|
|
||||||
await page.click('button:has-text("Reminders")');
|
|
||||||
await page.click('button:has-text("Add Reminder")');
|
|
||||||
await page.fill('input[name="title"]', 'Temporary Reminder');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Delete it
|
|
||||||
await page.click('[data-testid="delete-reminder"]');
|
|
||||||
await page.click('button:has-text("Delete")'); // Confirm
|
|
||||||
|
|
||||||
await expect(page.locator('text=Temporary Reminder')).not.toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show scheduled medication doses', async ({ page }) => {
|
|
||||||
// Add a medication first
|
|
||||||
await page.click('button:has-text("Add Medication")');
|
|
||||||
await page.fill('input[name="name"]', 'Scheduled Med');
|
|
||||||
await page.fill('input[name="dosage"]', '5mg');
|
|
||||||
await page.selectOption('select[name="frequency"]', 'daily');
|
|
||||||
await page.fill('input[name="times"]', '3'); // 3 times daily
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Should see scheduled doses for today
|
|
||||||
await expect(page.locator('text=Scheduled Med')).toBeVisible();
|
|
||||||
await expect(page.locator('button:has-text("Take")')).toHaveCount(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle missed doses', async ({ page }) => {
|
|
||||||
// This would test the logic for marking doses as missed
|
|
||||||
// when they pass their scheduled time
|
|
||||||
|
|
||||||
// Add medication with past schedule
|
|
||||||
await page.click('button:has-text("Add Medication")');
|
|
||||||
await page.fill('input[name="name"]', 'Past Due Med');
|
|
||||||
await page.fill('input[name="dosage"]', '10mg');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Simulate time passing or manually mark as missed
|
|
||||||
// This would depend on your app's specific implementation
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Optimized test utilities to reduce duplication
|
|
||||||
import { Page } from '@playwright/test';
|
|
||||||
|
|
||||||
export class TestUtils {
|
|
||||||
static async loginAsAdmin(page: Page): Promise<void> {
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await page.fill('input[type="password"]', 'admin123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
await page.waitForSelector('h1:has-text("Medication Reminder")');
|
|
||||||
}
|
|
||||||
|
|
||||||
static async loginAsUser(
|
|
||||||
page: Page,
|
|
||||||
email: string = 'testuser@example.com',
|
|
||||||
password: string = 'TestPassword123!'
|
|
||||||
): Promise<void> {
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', email);
|
|
||||||
await page.fill('input[type="password"]', password);
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
await page.waitForSelector('h1:has-text("Medication Reminder")');
|
|
||||||
}
|
|
||||||
|
|
||||||
static async waitForApp(page: Page): Promise<void> {
|
|
||||||
await page.waitForSelector('h1:has-text("Medication Reminder")');
|
|
||||||
}
|
|
||||||
|
|
||||||
static async openModal(page: Page, buttonText: string): Promise<void> {
|
|
||||||
await page.click(`button:has-text("${buttonText}")`);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async closeModal(page: Page): Promise<void> {
|
|
||||||
await page.click('button:has-text("Close")');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
|
||||||
|
|
||||||
test.describe('User Interface and Navigation', () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
// Login as admin
|
|
||||||
await page.goto('/');
|
|
||||||
await page.fill('input[type="email"]', 'admin@localhost');
|
|
||||||
await page.fill('input[type="password"]', 'admin123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display main navigation elements', async ({ page }) => {
|
|
||||||
await expect(
|
|
||||||
page.locator('button:has-text("Add Medication")')
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(page.locator('button:has-text("Manage")')).toBeVisible();
|
|
||||||
await expect(page.locator('button:has-text("History")')).toBeVisible();
|
|
||||||
await expect(page.locator('button:has-text("Stats")')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should toggle theme', async ({ page }) => {
|
|
||||||
// Click theme switcher
|
|
||||||
await page.click('[data-testid="theme-switcher"]');
|
|
||||||
|
|
||||||
// Check if dark mode is applied
|
|
||||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
|
||||||
|
|
||||||
// Toggle back to light mode
|
|
||||||
await page.click('[data-testid="theme-switcher"]');
|
|
||||||
await expect(page.locator('html')).not.toHaveClass(/dark/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should open and close account modal', async ({ page }) => {
|
|
||||||
// Click account button
|
|
||||||
await page.click('button:has-text("Account")');
|
|
||||||
|
|
||||||
// Should show account modal
|
|
||||||
await expect(page.locator('text=Account Settings')).toBeVisible();
|
|
||||||
|
|
||||||
// Close modal
|
|
||||||
await page.click('button:has-text("Close")');
|
|
||||||
await expect(page.locator('text=Account Settings')).not.toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should open stats modal', async ({ page }) => {
|
|
||||||
await page.click('button:has-text("Stats")');
|
|
||||||
|
|
||||||
await expect(page.locator('text=Medication Statistics')).toBeVisible();
|
|
||||||
await expect(page.locator('text=Weekly Adherence')).toBeVisible();
|
|
||||||
|
|
||||||
await page.click('button:has-text("Close")');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show current time and date', async ({ page }) => {
|
|
||||||
// Should display current time somewhere on the page
|
|
||||||
const timeElement = page.locator('[data-testid="current-time"]');
|
|
||||||
await expect(timeElement).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle responsive design', async ({ page }) => {
|
|
||||||
// Test mobile viewport
|
|
||||||
await page.setViewportSize({ width: 375, height: 667 });
|
|
||||||
|
|
||||||
// Should still show main elements
|
|
||||||
await expect(page.locator('h1')).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.locator('button:has-text("Add Medication")')
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
// Reset to desktop
|
|
||||||
await page.setViewportSize({ width: 1280, height: 720 });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should search medications', async ({ page }) => {
|
|
||||||
// Add a test medication first
|
|
||||||
await page.click('button:has-text("Add Medication")');
|
|
||||||
await page.fill('input[name="name"]', 'Searchable Medicine');
|
|
||||||
await page.fill('input[name="dosage"]', '10mg');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
|
|
||||||
// Use search functionality
|
|
||||||
await page.fill('input[placeholder*="search"]', 'Searchable');
|
|
||||||
|
|
||||||
// Should show filtered results
|
|
||||||
await expect(page.locator('text=Searchable Medicine')).toBeVisible();
|
|
||||||
|
|
||||||
// Clear search
|
|
||||||
await page.fill('input[placeholder*="search"]', '');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should logout user', async ({ page }) => {
|
|
||||||
// Click logout (usually in avatar dropdown)
|
|
||||||
await page.click('[data-testid="avatar-dropdown"]');
|
|
||||||
await page.click('button:has-text("Logout")');
|
|
||||||
|
|
||||||
// Should return to login page
|
|
||||||
await expect(page.locator('h2')).toContainText(['Sign In', 'Login']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user