Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. # Fixtures Composition with mergeTests
  2. ## Principle
  3. 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.
  4. ## Rationale
  5. Using fixtures from multiple sources requires combining them:
  6. - Importing from multiple fixture files is verbose
  7. - Name conflicts between fixtures
  8. - Duplicate fixture definitions
  9. - No clear single test object
  10. Playwright's `mergeTests` provides:
  11. - **Single test object**: All fixtures in one import
  12. - **Conflict resolution**: Handles name collisions automatically
  13. - **Composition pattern**: Mix utilities, custom fixtures, third-party fixtures
  14. - **Type safety**: Full TypeScript support for merged fixtures
  15. - **Maintainability**: One place to manage all fixtures
  16. ## Pattern Examples
  17. ### Example 1: Basic Fixture Merging
  18. **Context**: Combine multiple playwright-utils fixtures into single test object.
  19. **Implementation**:
  20. ```typescript
  21. // playwright/support/merged-fixtures.ts
  22. import { mergeTests } from '@playwright/test';
  23. import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
  24. import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
  25. import { test as recurseFixture } from '@seontechnologies/playwright-utils/recurse/fixtures';
  26. // Merge all fixtures
  27. export const test = mergeTests(apiRequestFixture, authFixture, recurseFixture);
  28. export { expect } from '@playwright/test';
  29. ```
  30. ```typescript
  31. // In your tests - import from merged fixtures
  32. import { test, expect } from '../support/merged-fixtures';
  33. test('all utilities available', async ({
  34. apiRequest, // From api-request fixture
  35. authToken, // From auth fixture
  36. recurse, // From recurse fixture
  37. }) => {
  38. // All fixtures available in single test signature
  39. const { body } = await apiRequest({
  40. method: 'GET',
  41. path: '/api/protected',
  42. headers: { Authorization: `Bearer ${authToken}` },
  43. });
  44. await recurse(
  45. () => apiRequest({ method: 'GET', path: `/status/${body.id}` }),
  46. (res) => res.body.ready === true,
  47. );
  48. });
  49. ```
  50. **Key Points**:
  51. - Create one `merged-fixtures.ts` per project
  52. - Import test object from merged fixtures in all test files
  53. - All utilities available without multiple imports
  54. - Type-safe access to all fixtures
  55. ### Example 2: Combining with Custom Fixtures
  56. **Context**: Add project-specific fixtures alongside playwright-utils.
  57. **Implementation**:
  58. ```typescript
  59. // playwright/support/custom-fixtures.ts - Your project fixtures
  60. import { test as base } from '@playwright/test';
  61. import { createUser } from './factories/user-factory';
  62. import { seedDatabase } from './helpers/db-seeder';
  63. export const test = base.extend({
  64. // Custom fixture 1: Auto-seeded user
  65. testUser: async ({ request }, use) => {
  66. const user = await createUser({ role: 'admin' });
  67. await seedDatabase('users', [user]);
  68. await use(user);
  69. // Cleanup happens automatically
  70. },
  71. // Custom fixture 2: Database helpers
  72. db: async ({}, use) => {
  73. await use({
  74. seed: seedDatabase,
  75. clear: () => seedDatabase.truncate(),
  76. });
  77. },
  78. });
  79. // playwright/support/merged-fixtures.ts - Combine everything
  80. import { mergeTests } from '@playwright/test';
  81. import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
  82. import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
  83. import { test as customFixtures } from './custom-fixtures';
  84. export const test = mergeTests(
  85. apiRequestFixture,
  86. authFixture,
  87. customFixtures, // Your project fixtures
  88. );
  89. export { expect } from '@playwright/test';
  90. ```
  91. ```typescript
  92. // In tests - all fixtures available
  93. import { test, expect } from '../support/merged-fixtures';
  94. test('using mixed fixtures', async ({
  95. apiRequest, // playwright-utils
  96. authToken, // playwright-utils
  97. testUser, // custom
  98. db, // custom
  99. }) => {
  100. // Use playwright-utils
  101. const { body } = await apiRequest({
  102. method: 'GET',
  103. path: `/api/users/${testUser.id}`,
  104. headers: { Authorization: `Bearer ${authToken}` },
  105. });
  106. // Use custom fixture
  107. await db.clear();
  108. });
  109. ```
  110. **Key Points**:
  111. - Custom fixtures extend `base` test
  112. - Merge custom with playwright-utils fixtures
  113. - All available in one test signature
  114. - Maintainable separation of concerns
  115. ### Example 3: Full Utility Suite Integration
  116. **Context**: Production setup with all core playwright-utils and custom fixtures.
  117. **Implementation**:
  118. ```typescript
  119. // playwright/support/merged-fixtures.ts
  120. import { mergeTests } from '@playwright/test';
  121. // Playwright utils fixtures
  122. import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
  123. import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
  124. import { test as interceptFixture } from '@seontechnologies/playwright-utils/intercept-network-call/fixtures';
  125. import { test as recurseFixture } from '@seontechnologies/playwright-utils/recurse/fixtures';
  126. import { test as networkRecorderFixture } from '@seontechnologies/playwright-utils/network-recorder/fixtures';
  127. // Custom project fixtures
  128. import { test as customFixtures } from './custom-fixtures';
  129. // Merge everything
  130. export const test = mergeTests(apiRequestFixture, authFixture, interceptFixture, recurseFixture, networkRecorderFixture, customFixtures);
  131. export { expect } from '@playwright/test';
  132. ```
  133. ```typescript
  134. // In tests
  135. import { test, expect } from '../support/merged-fixtures';
  136. test('full integration', async ({
  137. page,
  138. context,
  139. apiRequest,
  140. authToken,
  141. interceptNetworkCall,
  142. recurse,
  143. networkRecorder,
  144. testUser, // custom
  145. }) => {
  146. // All utilities + custom fixtures available
  147. await networkRecorder.setup(context);
  148. const usersCall = interceptNetworkCall({ url: '**/api/users' });
  149. await page.goto('/users');
  150. const { responseJson } = await usersCall;
  151. expect(responseJson).toContainEqual(expect.objectContaining({ id: testUser.id }));
  152. });
  153. ```
  154. **Key Points**:
  155. - One merged-fixtures.ts for entire project
  156. - Combine all playwright-utils you use
  157. - Add custom project fixtures
  158. - Single import in all test files
  159. ### Example 4: Fixture Override Pattern
  160. **Context**: Override default options for specific test files or describes.
  161. **Implementation**:
  162. ```typescript
  163. import { test, expect } from '../support/merged-fixtures';
  164. // Override auth options for entire file
  165. test.use({
  166. authOptions: {
  167. userIdentifier: 'admin',
  168. environment: 'staging',
  169. },
  170. });
  171. test('uses admin on staging', async ({ authToken }) => {
  172. // Token is for admin user on staging environment
  173. });
  174. // Override for specific describe block
  175. test.describe('manager tests', () => {
  176. test.use({
  177. authOptions: {
  178. userIdentifier: 'manager',
  179. },
  180. });
  181. test('manager can access reports', async ({ page }) => {
  182. // Uses manager token
  183. await page.goto('/reports');
  184. });
  185. });
  186. ```
  187. **Key Points**:
  188. - `test.use()` overrides fixture options
  189. - Can override at file or describe level
  190. - Options merge with defaults
  191. - Type-safe overrides
  192. ### Example 5: Avoiding Fixture Conflicts
  193. **Context**: Handle name collisions when merging fixtures with same names.
  194. **Implementation**:
  195. ```typescript
  196. // If two fixtures have same name, last one wins
  197. import { test as fixture1 } from './fixture1'; // has 'user' fixture
  198. import { test as fixture2 } from './fixture2'; // also has 'user' fixture
  199. const test = mergeTests(fixture1, fixture2);
  200. // fixture2's 'user' overrides fixture1's 'user'
  201. // Better: Rename fixtures before merging
  202. import { test as base } from '@playwright/test';
  203. import { test as fixture1 } from './fixture1';
  204. const fixture1Renamed = base.extend({
  205. user1: fixture1._extend.user, // Rename to avoid conflict
  206. });
  207. const test = mergeTests(fixture1Renamed, fixture2);
  208. // Now both 'user1' and 'user' available
  209. // Best: Design fixtures without conflicts
  210. // - Prefix custom fixtures: 'myAppUser', 'myAppDb'
  211. // - Playwright-utils uses descriptive names: 'apiRequest', 'authToken'
  212. ```
  213. **Key Points**:
  214. - Last fixture wins in conflicts
  215. - Rename fixtures to avoid collisions
  216. - Design fixtures with unique names
  217. - Playwright-utils uses descriptive names (no conflicts)
  218. ## Recommended Project Structure
  219. ```
  220. playwright/
  221. ├── support/
  222. │ ├── merged-fixtures.ts # ⭐ Single test object for project
  223. │ ├── custom-fixtures.ts # Your project-specific fixtures
  224. │ ├── auth/
  225. │ │ ├── auth-fixture.ts # Auth wrapper (if needed)
  226. │ │ └── custom-auth-provider.ts
  227. │ ├── fixtures/
  228. │ │ ├── user-fixture.ts
  229. │ │ ├── db-fixture.ts
  230. │ │ └── api-fixture.ts
  231. │ └── utils/
  232. │ └── factories/
  233. └── tests/
  234. ├── api/
  235. │ └── users.spec.ts # import { test } from '../../support/merged-fixtures'
  236. ├── e2e/
  237. │ └── login.spec.ts # import { test } from '../../support/merged-fixtures'
  238. └── component/
  239. └── button.spec.ts # import { test } from '../../support/merged-fixtures'
  240. ```
  241. ## Benefits of Fixture Composition
  242. **Compared to direct imports:**
  243. ```typescript
  244. // ❌ Without mergeTests (verbose)
  245. import { test as base } from '@playwright/test';
  246. import { apiRequest } from '@seontechnologies/playwright-utils/api-request';
  247. import { getAuthToken } from './auth';
  248. import { createUser } from './factories';
  249. test('verbose', async ({ request }) => {
  250. const token = await getAuthToken();
  251. const user = await createUser();
  252. const response = await apiRequest({ request, method: 'GET', path: '/api/users' });
  253. // Manual wiring everywhere
  254. });
  255. // ✅ With mergeTests (clean)
  256. import { test } from '../support/merged-fixtures';
  257. test('clean', async ({ apiRequest, authToken, testUser }) => {
  258. const { body } = await apiRequest({ method: 'GET', path: '/api/users' });
  259. // All fixtures auto-wired
  260. });
  261. ```
  262. **Reduction:** ~10 lines per test → ~2 lines
  263. ## Related Fragments
  264. - `overview.md` - Installation and design principles
  265. - `api-request.md`, `auth-session.md`, `recurse.md` - Utilities to merge
  266. - `network-recorder.md`, `intercept-network-call.md`, `log.md` - Additional utilities
  267. ## Anti-Patterns
  268. **❌ Importing test from multiple fixture files:**
  269. ```typescript
  270. import { test } from '@seontechnologies/playwright-utils/api-request/fixtures';
  271. // Also need auth...
  272. import { test as authTest } from '@seontechnologies/playwright-utils/auth-session/fixtures';
  273. // Name conflict! Which test to use?
  274. ```
  275. **✅ Use merged fixtures:**
  276. ```typescript
  277. import { test } from '../support/merged-fixtures';
  278. // All utilities available, no conflicts
  279. ```
  280. **❌ Merging too many fixtures (kitchen sink):**
  281. ```typescript
  282. // Merging 20+ fixtures makes test signature huge
  283. const test = mergeTests(...20 different fixtures)
  284. test('my test', async ({ fixture1, fixture2, ..., fixture20 }) => {
  285. // Cognitive overload
  286. })
  287. ```
  288. **✅ Merge only what you actually use:**
  289. ```typescript
  290. // Merge the 4-6 fixtures your project actually needs
  291. const test = mergeTests(apiRequestFixture, authFixture, recurseFixture, customFixtures);
  292. ```