1+ import { APIRequestContext , APIResponse , request } from '@playwright/test' ;
2+ import { logger } from '../utils/logger' ;
3+ import { config } from '../../config/test.config' ;
4+
5+ export interface ApiClientOptions {
6+ baseURL ?: string ;
7+ extraHTTPHeaders ?: Record < string , string > ;
8+ timeout ?: number ;
9+ }
10+
11+ export interface RequestOptions {
12+ params ?: Record < string , string | number > ;
13+ headers ?: Record < string , string > ;
14+ timeout ?: number ;
15+ }
16+
17+ export class ApiClient {
18+ private context ! : APIRequestContext ;
19+ private baseURL : string ;
20+ private defaultHeaders : Record < string , string > ;
21+
22+ constructor ( options ?: ApiClientOptions ) {
23+ this . baseURL = options ?. baseURL || config . baseURL ;
24+ this . defaultHeaders = {
25+ 'Accept' : 'application/json' ,
26+ 'Content-Type' : 'application/json' ,
27+ ...options ?. extraHTTPHeaders
28+ } ;
29+ }
30+
31+ /**
32+ * Initialize the API request context
33+ */
34+ async init ( ) : Promise < void > {
35+ this . context = await request . newContext ( {
36+ baseURL : this . baseURL ,
37+ extraHTTPHeaders : this . defaultHeaders ,
38+ ignoreHTTPSErrors : true
39+ } ) ;
40+ logger . info ( 'API Client initialized' , { baseURL : this . baseURL } ) ;
41+ }
42+
43+ /**
44+ * Dispose the API request context
45+ */
46+ async dispose ( ) : Promise < void > {
47+ if ( this . context ) {
48+ await this . context . dispose ( ) ;
49+ logger . info ( 'API Client disposed' ) ;
50+ }
51+ }
52+
53+ /**
54+ * GET request
55+ */
56+ async get ( endpoint : string , options ?: RequestOptions ) : Promise < APIResponse > {
57+ logger . info ( `API GET Request` , { endpoint, params : options ?. params } ) ;
58+ const startTime = Date . now ( ) ;
59+
60+ try {
61+ const response = await this . context . get ( endpoint , {
62+ params : options ?. params ,
63+ headers : { ...this . defaultHeaders , ...options ?. headers } ,
64+ timeout : options ?. timeout
65+ } ) ;
66+
67+ const duration = Date . now ( ) - startTime ;
68+ this . logResponse ( response , 'GET' , endpoint , duration ) ;
69+ return response ;
70+ } catch ( error ) {
71+ logger . error ( 'API GET Request failed' , { endpoint, error } ) ;
72+ throw error ;
73+ }
74+ }
75+
76+ /**
77+ * POST request
78+ */
79+ async post ( endpoint : string , data ?: any , options ?: RequestOptions ) : Promise < APIResponse > {
80+ logger . info ( `API POST Request` , { endpoint, hasData : ! ! data } ) ;
81+ const startTime = Date . now ( ) ;
82+
83+ try {
84+ const response = await this . context . post ( endpoint , {
85+ data,
86+ params : options ?. params ,
87+ headers : { ...this . defaultHeaders , ...options ?. headers } ,
88+ timeout : options ?. timeout
89+ } ) ;
90+
91+ const duration = Date . now ( ) - startTime ;
92+ this . logResponse ( response , 'POST' , endpoint , duration ) ;
93+ return response ;
94+ } catch ( error ) {
95+ logger . error ( 'API POST Request failed' , { endpoint, error } ) ;
96+ throw error ;
97+ }
98+ }
99+
100+ /**
101+ * PUT request
102+ */
103+ async put ( endpoint : string , data ?: any , options ?: RequestOptions ) : Promise < APIResponse > {
104+ logger . info ( `API PUT Request` , { endpoint, hasData : ! ! data } ) ;
105+ const startTime = Date . now ( ) ;
106+
107+ try {
108+ const response = await this . context . put ( endpoint , {
109+ data,
110+ params : options ?. params ,
111+ headers : { ...this . defaultHeaders , ...options ?. headers } ,
112+ timeout : options ?. timeout
113+ } ) ;
114+
115+ const duration = Date . now ( ) - startTime ;
116+ this . logResponse ( response , 'PUT' , endpoint , duration ) ;
117+ return response ;
118+ } catch ( error ) {
119+ logger . error ( 'API PUT Request failed' , { endpoint, error } ) ;
120+ throw error ;
121+ }
122+ }
123+
124+ /**
125+ * PATCH request
126+ */
127+ async patch ( endpoint : string , data ?: any , options ?: RequestOptions ) : Promise < APIResponse > {
128+ logger . info ( `API PATCH Request` , { endpoint, hasData : ! ! data } ) ;
129+ const startTime = Date . now ( ) ;
130+
131+ try {
132+ const response = await this . context . patch ( endpoint , {
133+ data,
134+ params : options ?. params ,
135+ headers : { ...this . defaultHeaders , ...options ?. headers } ,
136+ timeout : options ?. timeout
137+ } ) ;
138+
139+ const duration = Date . now ( ) - startTime ;
140+ this . logResponse ( response , 'PATCH' , endpoint , duration ) ;
141+ return response ;
142+ } catch ( error ) {
143+ logger . error ( 'API PATCH Request failed' , { endpoint, error } ) ;
144+ throw error ;
145+ }
146+ }
147+
148+ /**
149+ * DELETE request
150+ */
151+ async delete ( endpoint : string , options ?: RequestOptions ) : Promise < APIResponse > {
152+ logger . info ( `API DELETE Request` , { endpoint } ) ;
153+ const startTime = Date . now ( ) ;
154+
155+ try {
156+ const response = await this . context . delete ( endpoint , {
157+ params : options ?. params ,
158+ headers : { ...this . defaultHeaders , ...options ?. headers } ,
159+ timeout : options ?. timeout
160+ } ) ;
161+
162+ const duration = Date . now ( ) - startTime ;
163+ this . logResponse ( response , 'DELETE' , endpoint , duration ) ;
164+ return response ;
165+ } catch ( error ) {
166+ logger . error ( 'API DELETE Request failed' , { endpoint, error } ) ;
167+ throw error ;
168+ }
169+ }
170+
171+ /**
172+ * Set authentication token
173+ */
174+ setAuthToken ( token : string ) : void {
175+ this . defaultHeaders [ 'Authorization' ] = `Bearer ${ token } ` ;
176+ logger . info ( 'Auth token set' ) ;
177+ }
178+
179+ /**
180+ * Remove authentication token
181+ */
182+ removeAuthToken ( ) : void {
183+ delete this . defaultHeaders [ 'Authorization' ] ;
184+ logger . info ( 'Auth token removed' ) ;
185+ }
186+
187+ /**
188+ * Set custom header
189+ */
190+ setHeader ( key : string , value : string ) : void {
191+ this . defaultHeaders [ key ] = value ;
192+ logger . debug ( 'Custom header set' , { key } ) ;
193+ }
194+
195+ /**
196+ * Get response body as JSON
197+ */
198+ async getJsonBody < T = any > ( response : APIResponse ) : Promise < T > {
199+ try {
200+ return await response . json ( ) ;
201+ } catch ( error ) {
202+ logger . error ( 'Failed to parse JSON response' , { error } ) ;
203+ throw error ;
204+ }
205+ }
206+
207+ /**
208+ * Get response body as text
209+ */
210+ async getTextBody ( response : APIResponse ) : Promise < string > {
211+ try {
212+ return await response . text ( ) ;
213+ } catch ( error ) {
214+ logger . error ( 'Failed to get text response' , { error } ) ;
215+ throw error ;
216+ }
217+ }
218+
219+ /**
220+ * Assert response status
221+ */
222+ async assertStatus ( response : APIResponse , expectedStatus : number ) : Promise < void > {
223+ const actualStatus = response . status ( ) ;
224+ if ( actualStatus !== expectedStatus ) {
225+ const body = await this . getTextBody ( response ) ;
226+ logger . error ( 'Status assertion failed' , {
227+ expected : expectedStatus ,
228+ actual : actualStatus ,
229+ body
230+ } ) ;
231+ throw new Error ( `Expected status ${ expectedStatus } , got ${ actualStatus } ` ) ;
232+ }
233+ logger . info ( 'Status assertion passed' , { status : expectedStatus } ) ;
234+ }
235+
236+ /**
237+ * Assert response is OK (200-299)
238+ */
239+ async assertOk ( response : APIResponse ) : Promise < void > {
240+ if ( ! response . ok ( ) ) {
241+ const body = await this . getTextBody ( response ) ;
242+ logger . error ( 'OK assertion failed' , {
243+ status : response . status ( ) ,
244+ body
245+ } ) ;
246+ throw new Error ( `Response not OK: ${ response . status ( ) } ` ) ;
247+ }
248+ logger . info ( 'OK assertion passed' , { status : response . status ( ) } ) ;
249+ }
250+
251+ /**
252+ * Log response details
253+ */
254+ private logResponse ( response : APIResponse , method : string , endpoint : string , duration : number ) : void {
255+ const status = response . status ( ) ;
256+ const statusText = response . statusText ( ) ;
257+ const level = status >= 400 ? 'error' : status >= 300 ? 'warn' : 'info' ;
258+
259+ logger [ level ] ( `API ${ method } Response` , {
260+ endpoint,
261+ status,
262+ statusText,
263+ duration : `${ duration } ms` ,
264+ ok : response . ok ( )
265+ } ) ;
266+ }
267+ }
0 commit comments