bmad-method-test-architecture-enterprise 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method-test-architecture-enterprise",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "Master Test Architect for quality strategy, test automation, and release gates",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bmad",
|
package/release_notes.md
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
## 🚀 What's New in v1.
|
|
1
|
+
## 🚀 What's New in v1.2.0
|
|
2
2
|
|
|
3
3
|
### ✨ New Features
|
|
4
|
-
- feat:
|
|
5
|
-
|
|
6
|
-
### 🐛 Bug Fixes
|
|
7
|
-
- fix: issues 22 23
|
|
4
|
+
- feat:enhance api-request with operation-based overload and update documentation
|
|
8
5
|
|
|
9
6
|
### 📦 Other Changes
|
|
10
|
-
- Merge pull request #
|
|
11
|
-
- addressed PR comments
|
|
12
|
-
- Merge pull request #26 from bmad-code-org/fix/issue-22-23
|
|
7
|
+
- Merge pull request #28 from bmad-code-org/feat/knowledge-update-pw-utils-3-14-0
|
|
13
8
|
|
|
14
9
|
|
|
15
10
|
## 📦 Installation
|
|
@@ -19,4 +14,4 @@ npx bmad-method install
|
|
|
19
14
|
# Select "Test Architect" from module menu
|
|
20
15
|
```
|
|
21
16
|
|
|
22
|
-
**Full Changelog**: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise/compare/v1.0
|
|
17
|
+
**Full Changelog**: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise/compare/v1.1.0...v1.2.0
|
|
@@ -371,6 +371,95 @@ test.describe('GraphQL API', () => {
|
|
|
371
371
|
- Check `body.errors` for GraphQL errors (not status code)
|
|
372
372
|
- Works for queries and mutations
|
|
373
373
|
|
|
374
|
+
### Example 8: Operation-Based Overload (OpenAPI / Code Generators)
|
|
375
|
+
|
|
376
|
+
**Context**: When using a code generator (orval, openapi-generator, custom scripts) that produces typed operation definitions from an OpenAPI spec, pass the operation object directly to `apiRequest`. This eliminates manual `method`/`path` extraction and `typeof` assertions while preserving full type inference for request body, response, and query parameters. Available since v3.14.0.
|
|
377
|
+
|
|
378
|
+
**Implementation**:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// Generated operation definition — structural typing, no import from playwright-utils needed
|
|
382
|
+
// type OperationShape = { path: string; method: 'POST'|'GET'|'PUT'|'DELETE'|'PATCH'|'HEAD'; response: unknown; request: unknown; query?: unknown }
|
|
383
|
+
|
|
384
|
+
import { test, expect } from '@seontechnologies/playwright-utils/api-request/fixtures';
|
|
385
|
+
|
|
386
|
+
// --- Basic usage: operation replaces method + path ---
|
|
387
|
+
test('should upsert person via operation overload', async ({ apiRequest }) => {
|
|
388
|
+
const { status, body } = await apiRequest({
|
|
389
|
+
operation: upsertPersonv2({ customerId }),
|
|
390
|
+
headers: getHeaders(customerId),
|
|
391
|
+
body: personInput, // compile-time typed as Schemas.PersonInput
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(status).toBe(200);
|
|
395
|
+
expect(body.id).toBeDefined(); // body typed as Schemas.Person
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// --- Typed query parameters (replaces string concatenation) ---
|
|
399
|
+
test('should list people with typed query', async ({ apiRequest }) => {
|
|
400
|
+
const { body } = await apiRequest({
|
|
401
|
+
operation: getPeoplev2({ customerId }),
|
|
402
|
+
headers: getHeaders(customerId),
|
|
403
|
+
query: { page: 0, page_size: 5 }, // typed from operation's query definition
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
expect(body.items).toHaveLength(5);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// --- Params escape hatch (pre-formatted query strings) ---
|
|
410
|
+
test('should fetch billing history with raw params', async ({ apiRequest }) => {
|
|
411
|
+
const { body } = await apiRequest({
|
|
412
|
+
operation: getBillingHistoryv2({ customerId }),
|
|
413
|
+
headers: getHeaders(customerId),
|
|
414
|
+
params: {
|
|
415
|
+
'filters[start_date]': getThisMonthTimestamp(),
|
|
416
|
+
'filters[date_type]': 'MONTH',
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
expect(body.entries.length).toBeGreaterThan(0);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// --- Works with recurse (polling) ---
|
|
424
|
+
test('should poll until person is reviewed', async ({ apiRequest, recurse }) => {
|
|
425
|
+
await recurse(
|
|
426
|
+
async () =>
|
|
427
|
+
apiRequest({
|
|
428
|
+
operation: getPersonv2({ customerId, hash }),
|
|
429
|
+
headers: getHeaders(customerId),
|
|
430
|
+
}),
|
|
431
|
+
(res) => {
|
|
432
|
+
expect(res.status).toBe(200);
|
|
433
|
+
expect(res.body.status).toBe('REVIEWED');
|
|
434
|
+
},
|
|
435
|
+
{ timeout: 30000, interval: 1000 },
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// --- Schema validation chains work identically ---
|
|
440
|
+
test('should create movie with schema validation', async ({ apiRequest }) => {
|
|
441
|
+
const { body } = await apiRequest({
|
|
442
|
+
operation: createMovieOp,
|
|
443
|
+
headers: commonHeaders(authToken),
|
|
444
|
+
body: movie,
|
|
445
|
+
}).validateSchema(CreateMovieResponseSchema, {
|
|
446
|
+
shape: { status: 200, data: { name: movie.name } },
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(body.data.id).toBeDefined();
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Key Points**:
|
|
454
|
+
|
|
455
|
+
- Pass `operation` instead of `method` + `path` — mutually exclusive at compile time
|
|
456
|
+
- Response body, request body, and query types inferred from operation definition
|
|
457
|
+
- Uses structural typing (duck typing) — works with any code generator producing `{ path, method, response, request, query? }`
|
|
458
|
+
- `query` field auto-serializes to bracket notation (`filters[type]=pep`, `ids[0]=10`)
|
|
459
|
+
- `params` escape hatch for pre-formatted strings — wins over `query` on conflict
|
|
460
|
+
- Fully composable with `recurse`, `validateSchema`, and all existing features
|
|
461
|
+
- `response`/`request`/`query` on the operation are type-level only — runtime never reads their values
|
|
462
|
+
|
|
374
463
|
## Comparison with Vanilla Playwright
|
|
375
464
|
|
|
376
465
|
| Vanilla Playwright | playwright-utils apiRequest |
|
|
@@ -393,6 +482,7 @@ test.describe('GraphQL API', () => {
|
|
|
393
482
|
- ✅ Tests requiring retry logic
|
|
394
483
|
- ✅ Background API calls in UI tests
|
|
395
484
|
- ✅ Contract testing support
|
|
485
|
+
- ✅ Type-safe API testing with OpenAPI-generated operations (v3.14.0+)
|
|
396
486
|
|
|
397
487
|
**Stick with vanilla Playwright for:**
|
|
398
488
|
|
|
@@ -440,3 +530,34 @@ const response: any = await apiRequest({ method: 'GET', path: '/users' });
|
|
|
440
530
|
const { body } = await apiRequest<User[]>({ method: 'GET', path: '/users' });
|
|
441
531
|
// body is typed as User[]
|
|
442
532
|
```
|
|
533
|
+
|
|
534
|
+
**❌ Mixing operation overload with explicit generics:**
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// Don't pass a generic when using operation — types are inferred from the operation
|
|
538
|
+
const { body } = await apiRequest<MyType>({
|
|
539
|
+
operation: getPersonv2({ customerId }),
|
|
540
|
+
headers: getHeaders(customerId),
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**✅ Let the operation infer the types:**
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
const { body } = await apiRequest({
|
|
548
|
+
operation: getPersonv2({ customerId }),
|
|
549
|
+
headers: getHeaders(customerId),
|
|
550
|
+
});
|
|
551
|
+
// body type inferred from operation.response
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**❌ Mixing operation with method/path:**
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
// Compile error — operation and method/path are mutually exclusive
|
|
558
|
+
await apiRequest({
|
|
559
|
+
operation: getPersonv2({ customerId }),
|
|
560
|
+
method: 'GET', // Error: method?: never
|
|
561
|
+
path: '/api/person', // Error: path?: never
|
|
562
|
+
});
|
|
563
|
+
```
|
|
@@ -197,6 +197,7 @@ test.describe('Orders API', () => {
|
|
|
197
197
|
- `validateSchema` throws if response doesn't match
|
|
198
198
|
- Built-in retry for transient failures
|
|
199
199
|
- Type-safe `body` access
|
|
200
|
+
- **Note**: If your project uses code-generated operations from an OpenAPI spec, see [Example 8](#example-8-operation-based-api-testing-openapi--code-generators) for the preferred `operation`-based overload (v3.14.0+)
|
|
200
201
|
|
|
201
202
|
### Example 3: Microservice-to-Microservice Testing
|
|
202
203
|
|
|
@@ -713,6 +714,69 @@ test.describe('Authenticated API Tests', () => {
|
|
|
713
714
|
- Test auth, expired tokens, and RBAC
|
|
714
715
|
- Pure API testing without UI
|
|
715
716
|
|
|
717
|
+
### Example 8: Operation-Based API Testing (OpenAPI / Code Generators)
|
|
718
|
+
|
|
719
|
+
**Context**: When your project uses code-generated operation definitions from an OpenAPI spec, leverage the operation-based overload of `apiRequest` (v3.14.0+) instead of manual `method`/`path` extraction. This eliminates `typeof` assertions and provides full type inference for request body, response, and query parameters.
|
|
720
|
+
|
|
721
|
+
**Implementation**:
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
// tests/api/operations.spec.ts
|
|
725
|
+
import { test, expect } from '@seontechnologies/playwright-utils/api-request/fixtures';
|
|
726
|
+
|
|
727
|
+
test.describe('API Tests with Generated Operations', () => {
|
|
728
|
+
test('should create entity with full type safety', async ({ apiRequest }) => {
|
|
729
|
+
// Operation object from code generator — contains path, method, and type info
|
|
730
|
+
const { status, body } = await apiRequest({
|
|
731
|
+
operation: createEntityOp({ workspaceId }),
|
|
732
|
+
headers: getHeaders(workspaceId),
|
|
733
|
+
body: entityInput, // Compile-time typed from operation.request
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
expect(status).toBe(201);
|
|
737
|
+
expect(body.id).toBeDefined(); // body typed from operation.response
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
test('should list with typed query parameters', async ({ apiRequest }) => {
|
|
741
|
+
// query field replaces manual string concatenation
|
|
742
|
+
const { body } = await apiRequest({
|
|
743
|
+
operation: listEntitiesOp({ workspaceId }),
|
|
744
|
+
headers: getHeaders(workspaceId),
|
|
745
|
+
query: { page: 0, page_size: 10, status: 'active' },
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
expect(body.items).toHaveLength(10);
|
|
749
|
+
expect(body.total).toBeGreaterThan(10);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test('should poll async operation until complete', async ({ apiRequest, recurse }) => {
|
|
753
|
+
const { body: job } = await apiRequest({
|
|
754
|
+
operation: startJobOp({ workspaceId }),
|
|
755
|
+
headers: getHeaders(workspaceId),
|
|
756
|
+
body: { type: 'export' },
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
await recurse(
|
|
760
|
+
async () =>
|
|
761
|
+
apiRequest({
|
|
762
|
+
operation: getJobOp({ workspaceId, jobId: job.id }),
|
|
763
|
+
headers: getHeaders(workspaceId),
|
|
764
|
+
}),
|
|
765
|
+
(res) => res.body.status === 'completed',
|
|
766
|
+
{ timeout: 60000, interval: 2000 },
|
|
767
|
+
);
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
**Key Points**:
|
|
773
|
+
|
|
774
|
+
- `operation` replaces `method` + `path` — mutually exclusive at compile time
|
|
775
|
+
- Types for body, response, and query all inferred from the operation definition
|
|
776
|
+
- Works with any code generator using structural typing (no imports from playwright-utils needed in generator)
|
|
777
|
+
- Composable with `recurse`, `validateSchema`, and all existing `apiRequest` features
|
|
778
|
+
- Preferred approach over `typeof operation.response` for generated operations
|
|
779
|
+
|
|
716
780
|
## API Test Configuration
|
|
717
781
|
|
|
718
782
|
### Playwright Config for API-Only Tests
|
|
@@ -38,17 +38,17 @@ npm install -D @seontechnologies/playwright-utils
|
|
|
38
38
|
|
|
39
39
|
### Core Testing Utilities
|
|
40
40
|
|
|
41
|
-
| Utility | Purpose
|
|
42
|
-
| -------------------------- |
|
|
43
|
-
| **api-request** | Typed HTTP client with schema validation and
|
|
44
|
-
| **recurse** | Polling for async operations, background jobs
|
|
45
|
-
| **auth-session** | Token persistence, multi-user, service-to-service
|
|
46
|
-
| **log** | Playwright report-integrated logging
|
|
47
|
-
| **file-utils** | CSV/XLSX/PDF/ZIP reading & validation
|
|
48
|
-
| **burn-in** | Smart test selection with git diff
|
|
49
|
-
| **network-recorder** | HAR record/playback for offline testing
|
|
50
|
-
| **intercept-network-call** | Network spy/stub with auto JSON parsing
|
|
51
|
-
| **network-error-monitor** | Automatic HTTP 4xx/5xx detection
|
|
41
|
+
| Utility | Purpose | Test Context |
|
|
42
|
+
| -------------------------- | ----------------------------------------------------------------------------- | ------------------ |
|
|
43
|
+
| **api-request** | Typed HTTP client with schema validation, retry, and operation-based overload | **API/Backend** |
|
|
44
|
+
| **recurse** | Polling for async operations, background jobs | **API/Backend** |
|
|
45
|
+
| **auth-session** | Token persistence, multi-user, service-to-service | **API/Backend/UI** |
|
|
46
|
+
| **log** | Playwright report-integrated logging | **API/Backend/UI** |
|
|
47
|
+
| **file-utils** | CSV/XLSX/PDF/ZIP reading & validation | **API/Backend/UI** |
|
|
48
|
+
| **burn-in** | Smart test selection with git diff | **CI/CD** |
|
|
49
|
+
| **network-recorder** | HAR record/playback for offline testing | UI only |
|
|
50
|
+
| **intercept-network-call** | Network spy/stub with auto JSON parsing | UI only |
|
|
51
|
+
| **network-error-monitor** | Automatic HTTP 4xx/5xx detection | UI only |
|
|
52
52
|
|
|
53
53
|
**Note**: 6 of 9 utilities work without a browser. Only 3 are UI-specific (network-recorder, intercept-network-call, network-error-monitor).
|
|
54
54
|
|
|
@@ -21,7 +21,7 @@ test-healing-patterns,Test Healing Patterns,"Common failure patterns and automat
|
|
|
21
21
|
selector-resilience,Selector Resilience,"Robust selector strategies and debugging techniques","selectors,locators,debugging,ui",knowledge/selector-resilience.md
|
|
22
22
|
timing-debugging,Timing Debugging,"Race condition identification and deterministic wait fixes","timing,async,debugging",knowledge/timing-debugging.md
|
|
23
23
|
overview,Playwright Utils Overview,"Installation, design principles, fixture patterns for API and UI testing","playwright-utils,fixtures,api,backend,ui",knowledge/overview.md
|
|
24
|
-
api-request,API Request,"Typed HTTP client, schema validation, retry logic for API and service testing","api,backend,service-testing,api-testing,playwright-utils",knowledge/api-request.md
|
|
24
|
+
api-request,API Request,"Typed HTTP client, schema validation, retry logic, operation-based overload for API and service testing","api,backend,service-testing,api-testing,playwright-utils,openapi,codegen,operation",knowledge/api-request.md
|
|
25
25
|
network-recorder,Network Recorder,"HAR record/playback, CRUD detection for offline UI testing","network,playwright-utils,ui,har",knowledge/network-recorder.md
|
|
26
26
|
auth-session,Auth Session,"Token persistence, multi-user, API and browser authentication","auth,playwright-utils,api,backend,jwt,token",knowledge/auth-session.md
|
|
27
27
|
intercept-network-call,Intercept Network Call,"Network spy/stub, JSON parsing for UI tests","network,playwright-utils,ui",knowledge/intercept-network-call.md
|