Ignore and untrack BMad directories
This commit is contained in:
@@ -1,382 +0,0 @@
|
||||
# Fixtures Composition with mergeTests
|
||||
|
||||
## Principle
|
||||
|
||||
Combine multiple Playwright fixtures using `mergeTests` to create a unified test object with all capabilities. Build composable test infrastructure by merging playwright-utils fixtures with custom project fixtures.
|
||||
|
||||
## Rationale
|
||||
|
||||
Using fixtures from multiple sources requires combining them:
|
||||
|
||||
- Importing from multiple fixture files is verbose
|
||||
- Name conflicts between fixtures
|
||||
- Duplicate fixture definitions
|
||||
- No clear single test object
|
||||
|
||||
Playwright's `mergeTests` provides:
|
||||
|
||||
- **Single test object**: All fixtures in one import
|
||||
- **Conflict resolution**: Handles name collisions automatically
|
||||
- **Composition pattern**: Mix utilities, custom fixtures, third-party fixtures
|
||||
- **Type safety**: Full TypeScript support for merged fixtures
|
||||
- **Maintainability**: One place to manage all fixtures
|
||||
|
||||
## Pattern Examples
|
||||
|
||||
### Example 1: Basic Fixture Merging
|
||||
|
||||
**Context**: Combine multiple playwright-utils fixtures into single test object.
|
||||
|
||||
**Implementation**:
|
||||
|
||||
```typescript
|
||||
// playwright/support/merged-fixtures.ts
|
||||
import { mergeTests } from '@playwright/test';
|
||||
import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
|
||||
import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
|
||||
import { test as recurseFixture } from '@seontechnologies/playwright-utils/recurse/fixtures';
|
||||
|
||||
// Merge all fixtures
|
||||
export const test = mergeTests(apiRequestFixture, authFixture, recurseFixture);
|
||||
|
||||
export { expect } from '@playwright/test';
|
||||
```
|
||||
|
||||
```typescript
|
||||
// In your tests - import from merged fixtures
|
||||
import { test, expect } from '../support/merged-fixtures';
|
||||
|
||||
test('all utilities available', async ({
|
||||
apiRequest, // From api-request fixture
|
||||
authToken, // From auth fixture
|
||||
recurse, // From recurse fixture
|
||||
}) => {
|
||||
// All fixtures available in single test signature
|
||||
const { body } = await apiRequest({
|
||||
method: 'GET',
|
||||
path: '/api/protected',
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
|
||||
await recurse(
|
||||
() => apiRequest({ method: 'GET', path: `/status/${body.id}` }),
|
||||
(res) => res.body.ready === true,
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
|
||||
- Create one `merged-fixtures.ts` per project
|
||||
- Import test object from merged fixtures in all test files
|
||||
- All utilities available without multiple imports
|
||||
- Type-safe access to all fixtures
|
||||
|
||||
### Example 2: Combining with Custom Fixtures
|
||||
|
||||
**Context**: Add project-specific fixtures alongside playwright-utils.
|
||||
|
||||
**Implementation**:
|
||||
|
||||
```typescript
|
||||
// playwright/support/custom-fixtures.ts - Your project fixtures
|
||||
import { test as base } from '@playwright/test';
|
||||
import { createUser } from './factories/user-factory';
|
||||
import { seedDatabase } from './helpers/db-seeder';
|
||||
|
||||
export const test = base.extend({
|
||||
// Custom fixture 1: Auto-seeded user
|
||||
testUser: async ({ request }, use) => {
|
||||
const user = await createUser({ role: 'admin' });
|
||||
await seedDatabase('users', [user]);
|
||||
await use(user);
|
||||
// Cleanup happens automatically
|
||||
},
|
||||
|
||||
// Custom fixture 2: Database helpers
|
||||
db: async ({}, use) => {
|
||||
await use({
|
||||
seed: seedDatabase,
|
||||
clear: () => seedDatabase.truncate(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// playwright/support/merged-fixtures.ts - Combine everything
|
||||
import { mergeTests } from '@playwright/test';
|
||||
import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
|
||||
import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
|
||||
import { test as customFixtures } from './custom-fixtures';
|
||||
|
||||
export const test = mergeTests(
|
||||
apiRequestFixture,
|
||||
authFixture,
|
||||
customFixtures, // Your project fixtures
|
||||
);
|
||||
|
||||
export { expect } from '@playwright/test';
|
||||
```
|
||||
|
||||
```typescript
|
||||
// In tests - all fixtures available
|
||||
import { test, expect } from '../support/merged-fixtures';
|
||||
|
||||
test('using mixed fixtures', async ({
|
||||
apiRequest, // playwright-utils
|
||||
authToken, // playwright-utils
|
||||
testUser, // custom
|
||||
db, // custom
|
||||
}) => {
|
||||
// Use playwright-utils
|
||||
const { body } = await apiRequest({
|
||||
method: 'GET',
|
||||
path: `/api/users/${testUser.id}`,
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
});
|
||||
|
||||
// Use custom fixture
|
||||
await db.clear();
|
||||
});
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
|
||||
- Custom fixtures extend `base` test
|
||||
- Merge custom with playwright-utils fixtures
|
||||
- All available in one test signature
|
||||
- Maintainable separation of concerns
|
||||
|
||||
### Example 3: Full Utility Suite Integration
|
||||
|
||||
**Context**: Production setup with all core playwright-utils and custom fixtures.
|
||||
|
||||
**Implementation**:
|
||||
|
||||
```typescript
|
||||
// playwright/support/merged-fixtures.ts
|
||||
import { mergeTests } from '@playwright/test';
|
||||
|
||||
// Playwright utils fixtures
|
||||
import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
|
||||
import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
|
||||
import { test as interceptFixture } from '@seontechnologies/playwright-utils/intercept-network-call/fixtures';
|
||||
import { test as recurseFixture } from '@seontechnologies/playwright-utils/recurse/fixtures';
|
||||
import { test as networkRecorderFixture } from '@seontechnologies/playwright-utils/network-recorder/fixtures';
|
||||
|
||||
// Custom project fixtures
|
||||
import { test as customFixtures } from './custom-fixtures';
|
||||
|
||||
// Merge everything
|
||||
export const test = mergeTests(apiRequestFixture, authFixture, interceptFixture, recurseFixture, networkRecorderFixture, customFixtures);
|
||||
|
||||
export { expect } from '@playwright/test';
|
||||
```
|
||||
|
||||
```typescript
|
||||
// In tests
|
||||
import { test, expect } from '../support/merged-fixtures';
|
||||
|
||||
test('full integration', async ({
|
||||
page,
|
||||
context,
|
||||
apiRequest,
|
||||
authToken,
|
||||
interceptNetworkCall,
|
||||
recurse,
|
||||
networkRecorder,
|
||||
testUser, // custom
|
||||
}) => {
|
||||
// All utilities + custom fixtures available
|
||||
await networkRecorder.setup(context);
|
||||
|
||||
const usersCall = interceptNetworkCall({ url: '**/api/users' });
|
||||
|
||||
await page.goto('/users');
|
||||
const { responseJson } = await usersCall;
|
||||
|
||||
expect(responseJson).toContainEqual(expect.objectContaining({ id: testUser.id }));
|
||||
});
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
|
||||
- One merged-fixtures.ts for entire project
|
||||
- Combine all playwright-utils you use
|
||||
- Add custom project fixtures
|
||||
- Single import in all test files
|
||||
|
||||
### Example 4: Fixture Override Pattern
|
||||
|
||||
**Context**: Override default options for specific test files or describes.
|
||||
|
||||
**Implementation**:
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '../support/merged-fixtures';
|
||||
|
||||
// Override auth options for entire file
|
||||
test.use({
|
||||
authOptions: {
|
||||
userIdentifier: 'admin',
|
||||
environment: 'staging',
|
||||
},
|
||||
});
|
||||
|
||||
test('uses admin on staging', async ({ authToken }) => {
|
||||
// Token is for admin user on staging environment
|
||||
});
|
||||
|
||||
// Override for specific describe block
|
||||
test.describe('manager tests', () => {
|
||||
test.use({
|
||||
authOptions: {
|
||||
userIdentifier: 'manager',
|
||||
},
|
||||
});
|
||||
|
||||
test('manager can access reports', async ({ page }) => {
|
||||
// Uses manager token
|
||||
await page.goto('/reports');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
|
||||
- `test.use()` overrides fixture options
|
||||
- Can override at file or describe level
|
||||
- Options merge with defaults
|
||||
- Type-safe overrides
|
||||
|
||||
### Example 5: Avoiding Fixture Conflicts
|
||||
|
||||
**Context**: Handle name collisions when merging fixtures with same names.
|
||||
|
||||
**Implementation**:
|
||||
|
||||
```typescript
|
||||
// If two fixtures have same name, last one wins
|
||||
import { test as fixture1 } from './fixture1'; // has 'user' fixture
|
||||
import { test as fixture2 } from './fixture2'; // also has 'user' fixture
|
||||
|
||||
const test = mergeTests(fixture1, fixture2);
|
||||
// fixture2's 'user' overrides fixture1's 'user'
|
||||
|
||||
// Better: Rename fixtures before merging
|
||||
import { test as base } from '@playwright/test';
|
||||
import { test as fixture1 } from './fixture1';
|
||||
|
||||
const fixture1Renamed = base.extend({
|
||||
user1: fixture1._extend.user, // Rename to avoid conflict
|
||||
});
|
||||
|
||||
const test = mergeTests(fixture1Renamed, fixture2);
|
||||
// Now both 'user1' and 'user' available
|
||||
|
||||
// Best: Design fixtures without conflicts
|
||||
// - Prefix custom fixtures: 'myAppUser', 'myAppDb'
|
||||
// - Playwright-utils uses descriptive names: 'apiRequest', 'authToken'
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
|
||||
- Last fixture wins in conflicts
|
||||
- Rename fixtures to avoid collisions
|
||||
- Design fixtures with unique names
|
||||
- Playwright-utils uses descriptive names (no conflicts)
|
||||
|
||||
## Recommended Project Structure
|
||||
|
||||
```
|
||||
playwright/
|
||||
├── support/
|
||||
│ ├── merged-fixtures.ts # ⭐ Single test object for project
|
||||
│ ├── custom-fixtures.ts # Your project-specific fixtures
|
||||
│ ├── auth/
|
||||
│ │ ├── auth-fixture.ts # Auth wrapper (if needed)
|
||||
│ │ └── custom-auth-provider.ts
|
||||
│ ├── fixtures/
|
||||
│ │ ├── user-fixture.ts
|
||||
│ │ ├── db-fixture.ts
|
||||
│ │ └── api-fixture.ts
|
||||
│ └── utils/
|
||||
│ └── factories/
|
||||
└── tests/
|
||||
├── api/
|
||||
│ └── users.spec.ts # import { test } from '../../support/merged-fixtures'
|
||||
├── e2e/
|
||||
│ └── login.spec.ts # import { test } from '../../support/merged-fixtures'
|
||||
└── component/
|
||||
└── button.spec.ts # import { test } from '../../support/merged-fixtures'
|
||||
```
|
||||
|
||||
## Benefits of Fixture Composition
|
||||
|
||||
**Compared to direct imports:**
|
||||
|
||||
```typescript
|
||||
// ❌ Without mergeTests (verbose)
|
||||
import { test as base } from '@playwright/test';
|
||||
import { apiRequest } from '@seontechnologies/playwright-utils/api-request';
|
||||
import { getAuthToken } from './auth';
|
||||
import { createUser } from './factories';
|
||||
|
||||
test('verbose', async ({ request }) => {
|
||||
const token = await getAuthToken();
|
||||
const user = await createUser();
|
||||
const response = await apiRequest({ request, method: 'GET', path: '/api/users' });
|
||||
// Manual wiring everywhere
|
||||
});
|
||||
|
||||
// ✅ With mergeTests (clean)
|
||||
import { test } from '../support/merged-fixtures';
|
||||
|
||||
test('clean', async ({ apiRequest, authToken, testUser }) => {
|
||||
const { body } = await apiRequest({ method: 'GET', path: '/api/users' });
|
||||
// All fixtures auto-wired
|
||||
});
|
||||
```
|
||||
|
||||
**Reduction:** ~10 lines per test → ~2 lines
|
||||
|
||||
## Related Fragments
|
||||
|
||||
- `overview.md` - Installation and design principles
|
||||
- `api-request.md`, `auth-session.md`, `recurse.md` - Utilities to merge
|
||||
- `network-recorder.md`, `intercept-network-call.md`, `log.md` - Additional utilities
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
**❌ Importing test from multiple fixture files:**
|
||||
|
||||
```typescript
|
||||
import { test } from '@seontechnologies/playwright-utils/api-request/fixtures';
|
||||
// Also need auth...
|
||||
import { test as authTest } from '@seontechnologies/playwright-utils/auth-session/fixtures';
|
||||
// Name conflict! Which test to use?
|
||||
```
|
||||
|
||||
**✅ Use merged fixtures:**
|
||||
|
||||
```typescript
|
||||
import { test } from '../support/merged-fixtures';
|
||||
// All utilities available, no conflicts
|
||||
```
|
||||
|
||||
**❌ Merging too many fixtures (kitchen sink):**
|
||||
|
||||
```typescript
|
||||
// Merging 20+ fixtures makes test signature huge
|
||||
const test = mergeTests(...20 different fixtures)
|
||||
|
||||
test('my test', async ({ fixture1, fixture2, ..., fixture20 }) => {
|
||||
// Cognitive overload
|
||||
})
|
||||
```
|
||||
|
||||
**✅ Merge only what you actually use:**
|
||||
|
||||
```typescript
|
||||
// Merge the 4-6 fixtures your project actually needs
|
||||
const test = mergeTests(apiRequestFixture, authFixture, recurseFixture, customFixtures);
|
||||
```
|
||||
Reference in New Issue
Block a user