Skip to content

Commit 84eb911

Browse files
We have added API testing REproting and network capturing as part of this commit
1 parent 5e7e6ac commit 84eb911

File tree

8 files changed

+1557
-0
lines changed

8 files changed

+1557
-0
lines changed

src/api/api-client.ts

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
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

Comments
 (0)