From 2650445aace81a3fadc468a480bb46235cff0be2 Mon Sep 17 00:00:00 2001 From: Jahidur Rahman Date: Thu, 25 Jun 2026 11:34:35 +0600 Subject: [PATCH] API Testing --- README.md | 411 +++++-------------------------- test-data/api-data.json | 13 + tests/all.spec.ts | 2 +- tests/headers.spec.ts | 13 + tests/patch.spec.ts | 84 +++++++ tests/post_from_jsondata.spec.ts | 64 +++++ 6 files changed, 239 insertions(+), 348 deletions(-) create mode 100644 test-data/api-data.json create mode 100644 tests/headers.spec.ts create mode 100644 tests/patch.spec.ts create mode 100644 tests/post_from_jsondata.spec.ts diff --git a/README.md b/README.md index 60c7367..aed0131 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,26 @@ -# Playwright API Testing +# Playwright API Testing -A test automation framework for REST API testing built with Playwright Test. Includes support for sequential and parallel test execution, detailed HTML reporting, and CI/CD integration. +A lightweight REST API test suite built with Playwright Test for the Restful Booker API. It covers GET, POST, PATCH, PUT, DELETE, header validation, and sequential flows, with support for data-driven tests from JSON. -## Features +## What’s included -- Comprehensive REST API testing with Playwright's request context -- TypeScript support for type-safe test code -- Parallel test execution with configurable workers -- HTML test reporting with screenshots and videos on failure -- Trace viewer for detailed debugging -- Pre-configured for CI/CD pipelines +- API tests for GET, POST, PATCH, PUT, DELETE and header validation +- Browser UI validation after an API call in `tests/get5.spec.ts` +- Data-driven POST test using `test-data/api-data.json` +- Sequential CRUD flow in `tests/all.spec.ts` +- GitHub Actions CI workflow at `.github/workflows/playwright.yml` +- HTML reporting with traces, screenshots, and videos on failure +- Playwright configuration for local and CI execution + +## Dependencies + +- `@playwright/test` ^1.61.0 +- `@types/node` ^26.0.0 ## Prerequisites -- Node.js 18.0 or higher -- npm 8.0 or higher +- Node.js 18 or higher +- npm 8 or higher ## Installation @@ -22,357 +28,68 @@ A test automation framework for REST API testing built with Playwright Test. Inc npm install ``` -## Running Tests +## Running tests Run all tests: + ```bash npx playwright test ``` -Run specific test file: -```bash -npx playwright test tests/get.spec.ts -``` +Run a single test file: -Run tests matching a pattern: ```bash -npx playwright test --grep "GET" +npx playwright test tests/get.spec.ts ``` -Run with UI debugging: -```bash -npx playwright test --ui -``` +Show the HTML report: -View test report: ```bash npx playwright show-report ``` -## Project Structure +## Project structure ``` tests/ - ├── all.spec.ts # Sequential CRUD operations - ├── get.spec.ts # GET request tests - ├── get2.spec.ts # Additional GET tests - ├── get3.spec.ts # GET with parameters - ├── get4.spec.ts # GET edge cases - ├── get5.spec.ts # GET performance tests - ├── post.spec.ts # POST and authentication tests - ├── post2.spec.ts # Advanced POST operations - └── post3.spec.ts # E-commerce API tests -playwright.config.ts # Test configuration -playwright-report/ # Generated test reports -test-results/ # Test artifacts and logs -``` - -## Configuration - -Base configuration in `playwright.config.ts`: - -```typescript -baseURL: 'https://restful-booker.herokuapp.com' - -extraHTTPHeaders: { - Accept: 'application/json', - 'Content-Type': 'application/json' -} -``` - -Test settings: -- **Parallel execution**: All tests run in parallel by default -- **Retries**: Disabled locally, enabled (2) on CI -- **Reporter**: HTML report with screenshots and videos on failure -- **Timeout**: Default 30 seconds per test - -## Writing Tests - -### GET Request - -```typescript -import { test, expect } from '@playwright/test' - -test('GET - Fetch all bookings', async ({ request }) => { - const response = await request.get('/booking') - - expect(response.status()).toBe(200) - const bookings = await response.json() - expect(Array.isArray(bookings)).toBeTruthy() -}) -``` - -### POST Request with Validation - -```typescript -import { test, expect } from '@playwright/test' - -test('POST - Create authentication token', async ({ request }) => { - const response = await request.post('/auth', { - data: { - username: 'admin', - password: 'password123' - } - }) - - expect(response.status()).toBe(200) - const body = await response.json() - expect(body.token).toBeDefined() - expect(typeof body.token).toBe('string') -}) -``` - -### Sequential CRUD Operations - -```typescript -import { test, expect } from '@playwright/test' - -test.describe.serial('CRUD Operations', () => { - let bookingID: number - - const bookingPayload = { - firstname: 'John', - lastname: 'Doe', - totalprice: 500, - depositpaid: true, - bookingdates: { - checkin: '2024-01-01', - checkout: '2026-01-01' - }, - additionalneeds: 'Gym Membership' - } - - test('Create booking', async ({ request }) => { - const response = await request.post('/booking', { - data: bookingPayload - }) - - expect(response.status()).toBe(200) - const body = await response.json() - bookingID = body.bookingid - }) - - test('Retrieve booking', async ({ request }) => { - const response = await request.get(`/booking/${bookingID}`) - - expect(response.status()).toBe(200) - const booking = await response.json() - expect(booking.firstname).toBe(bookingPayload.firstname) - }) - - test('Delete booking', async ({ request }) => { - const response = await request.delete(`/booking/${bookingID}`) - expect(response.status()).toBe(201) - }) -}) -``` - -### Custom Request Context - -```typescript -import { test, request } from '@playwright/test' - -let apiContext - -test.beforeAll(async () => { - apiContext = await request.newContext({ - baseURL: 'https://restful-booker.herokuapp.com', - extraHTTPHeaders: { - 'Accept': 'application/json' - } - }) -}) - -test('GET with custom context', async () => { - const response = await apiContext.get('/booking') - expect(response.status()).toBe(200) - console.log(await response.json()) -}) - -test.afterAll(async () => { - await apiContext.dispose() -}) -``` - -## Test Reports - -Playwright generates HTML reports after test execution, located in `playwright-report/`. - -Report contents: -- Test execution summary -- Passed, failed, and skipped test counts -- Execution time per test -- Screenshots and videos on failure -- Detailed error messages and logs - -View report: -```bash -npx playwright show-report -``` - -Test artifacts (screenshots, videos, traces) are stored in `test-results/`. - -## API Endpoints Covered - -### Authentication -- `POST /auth` - Generate authentication token - -### Bookings -- `GET /booking` - Retrieve all bookings -- `GET /booking/{id}` - Retrieve specific booking -- `POST /booking` - Create new booking -- `DELETE /booking/{id}` - Delete booking - -### Shopping Cart -- `POST /addtocart` - Add item to cart -- `POST /viewcart` - View cart contents -- `POST /deleteitem` - Remove item from cart - -## Best Practices - -1. **Use meaningful test names** - Test names should clearly describe what is being tested - ```typescript - test('POST - Create booking with valid data and verify 200 status', async ({ request }) => { - ``` - -2. **Follow Arrange-Act-Assert pattern** - Structure tests clearly with setup, execution, and validation - ```typescript - // Arrange - const payload = { firstname: 'John' } - // Act - const response = await request.post('/booking', { data: payload }) - // Assert - expect(response.status()).toBe(200) - ``` - -3. **Always validate status code first** - Check response status before parsing the body - ```typescript - const response = await request.get('/booking') - expect(response.status()).toBe(200) - const body = await response.json() - ``` - -4. **Use serial tests for dependent tests** - When tests depend on each other, use `test.describe.serial()` - ```typescript - test.describe.serial('CRUD Tests', () => { - test('CREATE', async () => { ... }) - test('READ', async () => { ... }) - }) - ``` - -5. **Separate test data from test logic** - Keep payload data separate for better maintainability - ```typescript - const testData = { - validBooking: { firstname: 'John' }, - invalidBooking: { firstname: '' } - } - ``` - -6. **Use environment variables for sensitive data** - Never hardcode credentials in tests - ```typescript - const apiToken = process.env.API_TOKEN - ``` - -## Advanced Features - -### Trace Viewer - -Debug failed tests with detailed execution traces: - -```bash -npx playwright show-trace test-results/[test-name]/trace.zip -``` - -### Request Timeout - -Set timeouts for API requests: - -```typescript -// Per-request -await request.get('/booking', { timeout: 5000 }) -``` - -## Debugging - -### Enable Debug Mode - -```bash -# Run with debug logs -PW_DEBUG=api npx playwright test - -# Interactive debugging with UI -npx playwright test --ui - -# Step through tests -npx playwright test --debug -``` - -### Inspect API Responses - -```typescript -test('Debug API response', async ({ request }) => { - const response = await request.get('/booking') - - console.log('Status:', response.status()) - console.log('Headers:', response.headers()) - console.log('Body:', await response.json()) -}) -``` - -## CI/CD Pipeline - -Automated testing is configured through GitHub Actions. Tests run automatically on push events to `main` and `dev` branches. - -### Workflow Configuration - -The workflow file (`.github/workflows/playwright.yml`) includes: - -- **Trigger**: Runs on push to main/dev branches or manual workflow dispatch -- **Environment**: Ubuntu latest -- **Node Version**: 22 -- **Timeout**: 60 minutes -- **Artifact Retention**: 30 days - -### Workflow Steps - -1. **Checkout** - Retrieves repository code -2. **Setup Node** - Installs Node.js 22 -3. **Install Dependencies** - Runs `npm ci` for clean dependency installation -4. **Install Browsers** - Downloads Playwright browsers with system dependencies -5. **Run Tests** - Executes all test suites -6. **Upload Reports** - Stores HTML reports as artifacts for 30 days - -### Manual Workflow Trigger - -Trigger tests manually via GitHub Actions interface: - -1. Go to Actions tab in repository -2. Select "Playwright Tests" workflow -3. Click "Run workflow" -4. Select branch and run - -### View Test Results - -Test reports are available as workflow artifacts: - -1. Open workflow run details -2. Navigate to "Artifacts" section -3. Download `playwright-report` zip file -4. Extract and open `index.html` in browser - -## Dependencies - -| Package | Version | Purpose | -|---------|---------|---------| -| @playwright/test | ^1.61.0 | API testing framework | -| @types/node | ^26.0.0 | TypeScript types for Node.js | - -## Resources - -- [Playwright Documentation](https://playwright.dev/) -- [Playwright Test API](https://playwright.dev/docs/api/class-test) -- [API Request Context](https://playwright.dev/docs/api/class-apirequestcontext) - -## License - -ISC + all.spec.ts # Sequential CRUD flow + get.spec.ts # GET request tests + get2.spec.ts # Additional GET tests + get3.spec.ts # GET tests with parameters + get4.spec.ts # GET edge case tests + get5.spec.ts # Additional GET scenarios + headers.spec.ts # Header and request validation tests + patch.spec.ts # PATCH request tests + post.spec.ts # POST request tests + post2.spec.ts # Advanced POST tests + post3.spec.ts # Additional POST scenarios + post_from_jsondata.spec.ts # POST test using JSON payload from test-data/api-data.json +playwright.config.ts # Playwright test configuration +playwright-report/ # Generated HTML reports +test-results/ # Saved test artifacts and logs +test-data/api-data.json # JSON payload used by data-driven test +``` + +## Configuration highlights + +Key settings from `playwright.config.ts`: + +- `baseURL`: `https://restful-booker.herokuapp.com` +- `extraHTTPHeaders`: `Accept: application/json`, `Content-Type: application/json` +- `fullyParallel`: true +- `retries`: `0` locally, `2` on CI +- `reporter`: `html` +- `trace`: `on-first-retry` +- `screenshot`: `only-on-failure` +- `video`: `retain-on-failure` + +## CI/CD Integration + +This repository includes a GitHub Actions workflow at `.github/workflows/playwright.yml`. +It runs on `push` to `main` and `dev`, installs dependencies, installs Playwright browsers, executes `npx playwright test`, and uploads the generated `playwright-report/` as an artifact. + +## Notes + +- The project uses Playwright’s API testing features, not browser UI automation. +- `test-data/api-data.json` contains a reusable booking payload for POST-based tests. +- Generated reports are available in `playwright-report/` after running tests. diff --git a/test-data/api-data.json b/test-data/api-data.json new file mode 100644 index 0000000..235bfa6 --- /dev/null +++ b/test-data/api-data.json @@ -0,0 +1,13 @@ +{ + "orderPayload": { + "firstname": "Jim", + "lastname": "Brown", + "totalprice": 111, + "depositpaid": true, + "bookingdates": { + "checkin": "2018-01-01", + "checkout": "2019-01-01" + }, + "additionalneeds": "Breakfast" + } +} \ No newline at end of file diff --git a/tests/all.spec.ts b/tests/all.spec.ts index dd45515..05e1483 100644 --- a/tests/all.spec.ts +++ b/tests/all.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '@playwright/test' -test.describe.serial('CREATE -> GET, DELETE -> GET', () => { +test.describe.serial('CREATE -> GET, UPDATE -> GET, DELETE -> GET', () => { let bookingID: number let tokenID: string diff --git a/tests/headers.spec.ts b/tests/headers.spec.ts new file mode 100644 index 0000000..e386bcd --- /dev/null +++ b/tests/headers.spec.ts @@ -0,0 +1,13 @@ +import {test, expect} from '@playwright/test' + +test('Headers verification', async({request})=>{ + const response = await request.get('https://restful-booker.herokuapp.com/booking/1') + const body = await response.headers() + // console.log(body) + expect(body.server).toEqual("Heroku") + expect(body["x-powered-by"]).toEqual("Express") + + const bodyArray = await response.headersArray() + // console.log(bodyArray) + +}) \ No newline at end of file diff --git a/tests/patch.spec.ts b/tests/patch.spec.ts new file mode 100644 index 0000000..15545df --- /dev/null +++ b/tests/patch.spec.ts @@ -0,0 +1,84 @@ +import { test, expect } from '@playwright/test' + +test.describe.serial('CREATE -> PATCH -> GET', () => { + + let tokenID: string + let bookingID: number + const bookingPayload = { + "firstname": "James", + "lastname": "Brown", + "totalprice": 111, + "depositpaid": true, + "bookingdates": { + "checkin": "2018-01-01", + "checkout": "2019-01-01" + }, + "additionalneeds": "Breakfast" + } + + const partialPayload = { + "firstname": "Rasel", + "lastname": "Ahmed" + } + + test('CREATE TOKEN: AUTH', async ({ request }) => { + const response = await request.post('https://restful-booker.herokuapp.com/auth', + { + data: { + "username": "admin", + "password": "password123" + } + } + ) + const body = await response.json() + tokenID = body.token + + }) + + test('CREATE', async ({ request }) => { + const response = await request.post('https://restful-booker.herokuapp.com/booking', + { + data: bookingPayload + } + ) + const body = await response.json() + bookingID = body.bookingid + console.log("Created bookingID: ", bookingID) + expect(body.booking).toMatchObject(bookingPayload) + }) + + test('PATCH', async ({ request }) => { + const response = await request.patch(`https://restful-booker.herokuapp.com/booking/${bookingID}`, + { + headers: { + Cookie: `token=${tokenID}` + }, + data: partialPayload + } + ) + const body = await response.json() + expect(response.status()).toBe(200) + + // NOW GET call to validate the PATCH request + + const getResponse = await request.get(`https://restful-booker.herokuapp.com/booking/${bookingID}`) + const getBody = await getResponse.json() + + expect(getResponse.status()).toBe(200) + expect(getBody.firstname).toBe('Rasel') + expect(getBody.lastname).toBe('Ahmed') + + // validate non updated fields are remained as it is + expect(getBody).toEqual({ + "firstname": "Rasel", + "lastname": "Ahmed", + "totalprice": 111, + "depositpaid": true, + "bookingdates": { + "checkin": "2018-01-01", + "checkout": "2019-01-01" + }, + "additionalneeds": "Breakfast" + }) + }) +}) \ No newline at end of file diff --git a/tests/post_from_jsondata.spec.ts b/tests/post_from_jsondata.spec.ts new file mode 100644 index 0000000..578c5b5 --- /dev/null +++ b/tests/post_from_jsondata.spec.ts @@ -0,0 +1,64 @@ +import { test, expect } from '@playwright/test' +import apiData from '../test-data/api-data.json' + +test.describe.serial('Test from JSON data', () => { + + let bookingID: number + let tokenID: string + test('TOKEN Creation', async ({ request }) => { + const response = await request.post('https://restful-booker.herokuapp.com/auth', + { + data: { + "username": "admin", + "password": "password123" + } + } + ) + const body = await response.json() + tokenID = body.token + + }) + + test('Create booking', async ({ request }) => { + const respone = await request.post('https://restful-booker.herokuapp.com/booking', + { + data: apiData.orderPayload + } + ) + const body = await respone.json() + + bookingID = body.bookingid + expect(respone.status()).toBe(200) + + }) + + test('GET created booking', async ({ request }) => { + const response = await request.get(`https://restful-booker.herokuapp.com/booking/${bookingID}`) + const body = await response.json() + + expect(response.status()).toBe(200) + expect(body).toEqual(apiData.orderPayload) + }) + + test('DELETE booking', async ({ request }) => { + const response = await request.delete(`https://restful-booker.herokuapp.com/booking/${bookingID}`, + { + headers: { + Cookie: `token=${tokenID}` + } + } + ) + const body = await response.text() + expect(response.status()).toBe(201) + expect(body).toEqual("Created") + }) + + test('GET deleted booking', async({request})=>{ + const response = await request.get(`https://restful-booker.herokuapp.com/booking/${bookingID}`) + const body = await response.text() + + expect(response.status()).toBe(404) + expect(body).toEqual("Not Found") + }) + +}) \ No newline at end of file