UNPKG

tm-playwright-framework

Version:

Playwright Cucumber TS framework - The easiest way to learn

304 lines (303 loc) 12.3 kB
/** * PlaywrightRequestBuilder * * This file defines the `PlaywrightRequestBuilder` class, which provides a fluent API for building and sending HTTP requests * using Playwright's `APIRequestContext`. It supports various HTTP methods (GET, POST, PUT, DELETE) and allows customization * of request options such as headers, query parameters, authentication, and more. * * Key Features: * - Fluent API for setting request options (e.g., headers, timeout, method, URL, etc.). * - Support for request templates to standardize API calls. * - Retry logic for handling transient failures. * - Integration with Playwright's `APIRequestContext` for sending requests. * - Automatic response handling and logging. * - Assertion of expected status codes and response validation. * * Usage: * 1. Create an instance of `PlaywrightRequestBuilder`. * 2. Chain methods to configure the request (e.g., `setMethod`, `setURL`, `setHeaders`). * 3. Call `send()` or `sendWithRetries()` to execute the request. * * Example: * ```typescript * const requestBuilder = new PlaywrightRequestBuilder(); * await requestBuilder * .setMethod('GET') * .setURL('https://example.com/api') * .setHeaders({ Authorization: 'Bearer token' }) * .send(); * ``` * * Dependencies: * - Playwright for HTTP request handling. * - JSONPath for extracting data from JSON templates. * - fs-extra for file system operations. * - qs for query string manipulation. * - tm-playwright-framework for integration with the testing framework. * * * @author Sasitharan, Govindharam * @reviewer Sahoo, AshokKumar * @version 1.0 - 1st-JUNE-2025 */ import { JSONPath } from 'jsonpath-plus'; import { request } from 'playwright'; import fs from "fs-extra"; import qs from 'querystring'; import { fixture } from 'tm-playwright-framework/dist/hooks/pageFixture.js'; import { buildRequest, writeResponse } from 'tm-playwright-framework/dist/api/api_utility.js'; import { expect } from '@playwright/test'; import config from 'tm-playwright-framework/dist/config/playwright.config.js'; let baseURL; class PlaywrightRequestBuilder { constructor() { this.options = {}; // Using a plain object for options this.context = null; // Initialize context as null } // Initialize Playwright's request context async initContext() { if (!this.context) { this.context = await request.newContext({}); } return this.context; } // Set timeout in milliseconds setTimeout(ms) { this.options.timeout = ms; return this; } // Set additional HTTP headers setHeaders(headers) { this.options.headers = { ...this.options.headers, ...headers, }; return this; } updateURL(findWhat, replaceWith) { try { this.options.url = this.options.url?.replace(findWhat, replaceWith); } catch (Error) { console.log(Error); fixture.logger.error(Error.message); fixture.logger.error(Error.stack); } return this; } setTemplate(templateName, appBaseURL) { try { if (!appBaseURL) { if (!baseURL) baseURL = config.use?.baseURL; if (!baseURL) baseURL = process.env.API_BASEURL; } else { baseURL = appBaseURL; } this.options.template = templateName; let fileData = JSON.parse(fs.readFileSync(`${process.env.API_TEMPLATE_PATH}/${templateName}.json`, "utf8")); const contentType = JSONPath({ json: fileData, path: '$.Content-Type' })[0]; const accept = JSONPath({ json: fileData, path: '$.Accept' })[0]; const url = JSONPath({ json: fileData, path: '$.EndPoint' })[0]; this.options.method = JSONPath({ json: fileData, path: '$.Method' })[0]; if (url.toLowerCase().includes("http")) this.options.url = `${url}`; else this.options.url = `${baseURL}${url}`; this.setHeaders({ 'Content-Type': `${contentType}`, Accept: `${accept}` }); } catch (Error) { console.log(Error); fixture.logger.error(Error.message); fixture.logger.error(Error.stack); } return this; } // Set proxy server for the request setProxy(proxy) { this.options.proxy = { server: proxy }; return this; } // Set if request should fail on non-2xx status codes setFailOnStatusCode(fail) { this.options.failOnStatusCode = fail; return this; } // Set the request method (e.g., GET, POST) setMethod(method) { this.options.method = method.toUpperCase(); return this; } // Set the request URL setURL(url) { this.options.url = url; return this; } // Set the body of the request (for POST, PUT, etc.) setForm(form) { this.options.form = qs.stringify(buildRequest(form, this.options.template, this.options.iteration)); return this; } // Set the body of the request (for POST, PUT, etc.) setBody(body) { this.options.data = buildRequest(body, this.options.template, this.options.iteration); // Use 'data' for body content in Playwright API return this; } // Set the Iteration of the request (for POST, PUT, etc.) setIteration(iteration) { this.options.iteration = iteration; // Use 'data' for body content in Playwright API return this; } // Set query parameters for the request setQueryParams(params) { const url = new URL(this.options.url); // We assume URL is set before Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, String(value)); }); this.options.url = url.toString(); return this; } // Set authentication (username and password) setAuth(username, password) { this.options.auth = { username, password }; return this; } // Set response type (used with custom logic for handling response data) setResponseType(type) { this.options.responseType = type; return this; } // Set the maximum number of retries for the request setRetries(retries) { this.options.retries = retries; return this; } // Set the maximum number of retries for the request setExpectedStatusCode(expectedCode) { this.options.expectedCode = expectedCode; return this; } // Send the request using Playwright's APIRequestContext async send() { const context = await this.initContext(); // Make the request using the correct method (e.g., GET, POST, etc.) let response; switch (this.options.method) { case 'GET': response = await context.get(this.options.url, { headers: this.options.headers, timeout: this.options.timeout, failOnStatusCode: this.options.failOnStatusCode, }); fixture.logger.info("Executing API (GET): " + this.options.url); break; case 'POST': if (this.options.form) this.options.data = this.options.form.toString(); // Use form data for POST body response = await context.post(this.options.url, { headers: this.options.headers, data: this.options.data, // Use 'data' for POST body timeout: this.options.timeout, failOnStatusCode: this.options.failOnStatusCode, }); fixture.logger.info("Executing API (POST): " + this.options.url); break; case 'PUT': response = await context.put(this.options.url, { headers: this.options.headers, data: this.options.data, // Use 'data' for PUT body timeout: this.options.timeout, failOnStatusCode: this.options.failOnStatusCode, }); fixture.logger.info("Executing API (PUT): " + this.options.url); break; case 'DELETE': response = await context.delete(this.options.url, { headers: this.options.headers, timeout: this.options.timeout, failOnStatusCode: this.options.failOnStatusCode, }); fixture.logger.info("Executing API (DELETE): " + this.options.url); break; default: throw new Error(`Unsupported method: ${this.options.method}. Supported methods: GET, POST, PUT, DELETE`); } const contentType = response.headers()['content-type']; let APIResponse; if (contentType?.includes('application/json')) { APIResponse = await response.json(); writeResponse(APIResponse, this.options.template, this.options.iteration, ".json"); } else { APIResponse = await response.text(); writeResponse(APIResponse, this.options.template, this.options.iteration, ".txt"); } if (response.ok()) { this.message = `<font color=green><b>PASS: Assertion has been passed. Expected and Received Status Code: ${this.options.expectedCode} </b></font>`; } else { this.message = `<font color=red><b>FAIl: Assertion has been failed. Expected ${this.options.expectedCode} but Received: ${response.status()} </b></font>`; } if (this.options.expectedCode) expect(response.status()).toBe(this.options.expectedCode); expect(response.ok()).toBeTruthy(); fixture.logger.info(this.message); this.reponse = APIResponse; return response; } // Retry logic async sendWithRetries(retries = 3, delayMs = 1000) { let attempt = 0; while (attempt < retries) { try { const response = await this.send(); return response; } catch (err) { attempt++; console.warn(`Attempt ${attempt} failed: ${err.message}`); if (attempt === retries) { throw new Error(`Request failed after ${retries} attempts: ${err.message}`); } await new Promise(res => setTimeout(res, delayMs)); // delay before retrying } } throw new Error('Max retries exceeded'); } async sendWithConditionRetries(sendFn, options) { const { expectedStatus, jsonPath, expectedJsonValue, retries = 3, timeoutMs = 10000, delayMs = 1000, } = options; const startTime = Date.now(); let attempt = 0; while (attempt < retries && (Date.now() - startTime) < timeoutMs) { attempt++; try { const response = await sendFn(); const statusOk = expectedStatus === undefined || response.status() === expectedStatus; let valueOk = true; if (jsonPath && expectedJsonValue !== undefined) { const result = JSONPath({ path: jsonPath, json: response.body }); valueOk = result.length > 0 && result[0] === expectedJsonValue; } if (statusOk && valueOk) { return response; } else { console.warn(`Attempt ${attempt}: condition not met (status: ${response.status}, jsonPath match: ${valueOk}). Retrying...`); } } catch (err) { console.warn(`Attempt ${attempt} failed: ${err.message}`); } if (attempt < retries && (Date.now() - startTime) < timeoutMs) { await sleep(delayMs); } } throw new Error(`Request did not meet conditions after ${retries} attempts or timeout of ${timeoutMs}ms`); } } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } export { PlaywrightRequestBuilder };