A robust end-to-end testing framework built with Playwright and Cucumber BDD, designed for scalable, maintainable, and efficient test automation.
This framework combines the power of Playwright's modern browser automation capabilities with Cucumber's Behavior-Driven Development approach, enabling both technical and non-technical team members to collaborate effectively on test automation.
- Hybrid Testing: Combined UI and API testing in a single framework
- BDD Approach: Uses Cucumber for behavior-driven development
- Page Object Model: Clean separation of test logic and UI interactions
- Direct Page Instantiation: Simple and straightforward page object creation
- Service Factory Pattern: Efficient resource management for API components
- Multi-Browser Support: Tests run on Chrome, Firefox, and Safari
- Test Hooks: Flexible before/after hooks for setup and teardown operations
- Structured Logging: Configurable logging levels with detailed insights
- Comprehensive Reporting: Built-in HTML reporting with screenshots and traces
- Allure Reporting: Enhanced reporting with detailed visualizations and analytics
- Test Organization: Clear separation between UI and API tests
- Node.js (v14 or higher)
- npm (v6 or higher)
# Clone the repository
git clone https://github.com/your-org/playwright-bdd-hybrid-framework.git
# Install dependencies
npm installCreate a .env file in the root directory with the following structure:
# User Credentials for Test Authentication
TEST_USERNAME=tomsmith
TEST_PASSWORD=SuperSecretPassword!
# Test Environment Configuration
TEST_ENVIRONMENT=staging
TEST_TYPE=UI
# Logging Configuration
LOG_LEVEL=debug
# Execution Settings
HEADLESS=false
DEFAULT_TIMEOUT=30000
# Reporting Settings
SCREENSHOT_ON_FAILURE=true
VIDEO_RECORDING=false# Run all tests in headless mode
npm test
# Run tests with visible browser
npm run test:headed
# Run tests in UI mode for interactive debugging
npm run test:ui
# Run tests in debug mode with detailed logging
npm run test:debug
# Run UI tests in specific browsers
npm run test:ui-chrome
npm run test:ui-firefox
npm run test:ui-safari
# Run UI tests in all browsers
npm run test:ui-all
# Run API-only tests
npm run test:api
# Run UI and API tests in parallel
npm run test:parallel
# Clean Allure results and run tests with fresh reporting
npm run test:clean-run
# Run tests with custom log level
LOG_LEVEL=debug npm run test:api
LOG_LEVEL=warn npm run test:ui-chromeThe framework is built with a layered architecture that separates concerns and promotes reusability:
Feature files written in Gherkin syntax describe application behavior from a user's perspective.
Feature: Sample Login
Scenario: Successful Login
Given I am on the login page
When I enter valid credentials
Then I should be redirected to the dashboardStep definition files connect Gherkin steps to actual implementations using:
- Direct page object instantiation for UI interactions
- Service Factory for API client management
- Custom assertions for verification
Page objects provide a clean abstraction over UI elements and interactions:
- BasePage: Common utilities and methods shared across all pages
- LoginPage: Login page-specific interactions and locators
- DashboardPage: Dashboard/secure area page interactions
Direct Instantiation Pattern:
// Pages are instantiated directly when needed
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(username, password);
const dashboardPage = new DashboardPage(page);
await dashboardPage.isPageLoaded();API clients handle backend service calls:
- BaseApiClient: Core HTTP functionality (GET, POST, PUT, DELETE)
- AuthApiClient: Authentication-specific API operations
- ServiceFactory: Centralized client management with singleton pattern
Cross-cutting concerns that support the entire framework:
- Logger: Structured logging system with configurable levels
- Hooks: Test lifecycle management for setup and teardown
- Configuration: Environment-based configuration management
βββ features/ # Cucumber feature files
β βββ login.feature # Login functionality scenarios
βββ src/ # Source code
β βββ pages/ # Page Object Model classes
β β βββ BasePage.ts # Base page with common utilities
β β βββ LoginPage.ts # Login page interactions
β β βββ DashboardPage.ts # Dashboard/secure area page interactions
β βββ services/ # API Services
β β βββ api/ # API Clients
β β β βββ BaseApiClient.ts # Base API client with HTTP methods
β β β βββ AuthApiClient.ts # Authentication API methods
β β β βββ ServiceFactory.ts # Factory for managing API clients
β β βββ models/ # API Data Models
β β βββ ApiModels.ts # Interface definitions for API requests/responses
β βββ utils/ # Utilities and Helpers
β β βββ hooks.ts # Test lifecycle hooks for setup/teardown
β β βββ logger.ts # Configurable logging utility
β βββ steps/ # Step definitions
β βββ sampleSteps.ts # Implementation of test steps
βββ test-results/ # Test execution artifacts
βββ playwright-report/ # HTML test reports
βββ allure-results/ # Allure test results data
βββ allure-report/ # Generated Allure reports
βββ package.json # Project dependencies and scripts
βββ playwright.config.ts # Playwright configuration
βββ tsconfig.json # TypeScript configuration
The Page Object Model architecture separates UI interactions from test logic using direct instantiation:
Abstract base class with common utilities for all pages:
// Page loading utilities
async waitForPageLoad() {
log.debug('Waiting for page to reach networkidle state');
await this.page.waitForLoadState('networkidle');
log.debug('Page load completed');
}
// Verification methods
async verifyPageTitle(title: string) {
log.debug(`Verifying page title matches "${title}"`);
await expect(this.page).toHaveTitle(new RegExp(title));
log.debug('Page title verified successfully');
}Specialized page classes that extend BasePage:
// LoginPage - handles login page interactions
export class LoginPage extends BasePage {
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}Usage in Step Definitions:
// Direct instantiation - simple and clear
Given('I am on the login page', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.isPageLoaded();
});The API testing architecture provides a clean, modular approach to service interaction:
Foundation class for all API operations:
async post(url: string, options?: any): Promise<APIResponse> {
if (!this.apiContext) {
await this.init();
}
log.debug(`POST request to ${url}`, options);
const response = await this.apiContext.post(url, options);
log.debug(`POST response from ${url}: status ${response.status()}`);
return response;
}Manages API client instances with singleton pattern for efficient resource usage:
public async getAuthApiClient(): Promise<AuthApiClient> {
if (!this.serviceInstances.has('authApiClient')) {
log.debug('Creating new AuthApiClient instance');
const client = new AuthApiClient();
await client.init();
this.serviceInstances.set('authApiClient', client);
log.info('AuthApiClient instance created and initialized');
}
return this.serviceInstances.get('authApiClient');
}The framework implements Before and After hooks for test lifecycle management:
Executes before each test scenario for setup operations:
Before(async function ({ page }) {
log.debug('Executing Before hook');
// Add any setup logic here
})Executes after each test scenario for cleanup:
After(async function ({ page }) {
log.debug('Executing After hook for cleanup');
// Clean up Service objects (API clients)
const serviceFactory = ServiceFactory.getInstance();
await serviceFactory.dispose();
log.debug('After hook completed');
});Benefits of Hooks:
- Automatic resource cleanup after each test
- Prevents memory leaks
- Ensures test isolation
- Consistent setup and teardown logic
The framework includes a robust logging system for detailed test execution insights:
export enum LogLevel {
DEBUG = 1, // Most verbose - detailed debug information
INFO = 2, // General information about test progress
WARN = 3, // Warnings that don't fail tests but require attention
ERROR = 4, // Error conditions that affect test execution
NONE = 5 // No logging
}The logging level can be configured in multiple ways:
- Environment Variable: Set
LOG_LEVELin your environment - Command Line: Override for specific runs (
LOG_LEVEL=debug npm run test:api) - .env File: Default setting in your project's
.envfile - Configuration: Handled in
playwright.config.tswith fallback to 'info'
log.debug('Detailed debugging information');
log.info('General information about test execution');
log.warn('Warning message about potential issues');
log.error('Error occurred during test execution', error);The framework includes built-in HTML reporting for test results:
# Generate and view HTML report
npm run reportFeatures:
- Test execution timeline
- Pass/fail statistics
- Screenshots on failure
- Trace files for debugging
- Detailed error messages
Allure provides enhanced reporting capabilities with rich visualizations:
# Clean previous Allure results
npm run allure:clean
# Generate Allure report from test results
npm run allure:generate
# Open Allure report in browser
npm run allure:open
# Generate and open Allure report (combined command)
npm run allure:report
# Clean results, run tests, and generate report in one command
npm run test:clean-runAllure Report Features:
- Test execution trends over time
- Test categorization by severity and features
- Detailed test case history
- Behavior-driven test organization
- Rich graphs and charts
- Attachment support (screenshots, logs, videos)
- Before Hook - Executes setup operations
- Test Scenario Starts - BDD steps begin execution
- Page Instantiation - Pages created directly as needed
const loginPage = new LoginPage(page);
- Test Actions - Interactions performed via page objects
- Assertions - Verifications using Playwright's expect
- After Hook - Cleanup of API service resources
- Test Complete - Results captured in reports
- Before Hook - Executes setup operations
- Test Scenario Starts - BDD steps begin execution
- Service Factory - API client retrieved from factory
const serviceFactory = ServiceFactory.getInstance(); const authClient = await serviceFactory.getAuthApiClient();
- API Calls - HTTP requests executed
- Response Validation - Status codes and data verified
- After Hook - Cleanup of API contexts
- Test Complete - Results captured in reports
- Direct Instantiation: Create page objects when needed, no factory overhead
- Locator Strategy: Use Playwright's built-in locator strategies
- Reusability: Common methods in BasePage
- Single Responsibility: Each page object handles one page
- Factory Pattern: Use ServiceFactory for API client management
- Resource Cleanup: Automatic disposal in After hooks
- Error Handling: Graceful error handling with logging
- Response Validation: Type-safe response models
- Appropriate Levels: Use correct log level for each message
- Contextual Information: Include relevant details in logs
- Performance: Use DEBUG for verbose logs that can be disabled
- Production Runs: Use INFO or WARN level for CI/CD
- Feature Files: Group related scenarios
- Tags: Use @Smoke, @Regression for test categorization
- Step Reusability: Write generic, reusable step definitions
- Separation of Concerns: Keep UI and API logic separate
Issue: Tests failing due to timeouts
# Increase timeout in .env file
DEFAULT_TIMEOUT=60000Issue: Want to see detailed logs
# Run with debug logging
LOG_LEVEL=debug npm run testIssue: Need to debug specific test
# Run in debug mode with UI
npm run test:debugIssue: API context errors
- Ensure ServiceFactory cleanup in After hook is working
- Check API_BASE_URL in environment configuration
- Create new page class extending BasePage
- Define locators in constructor
- Implement page-specific methods
- Use direct instantiation in step definitions
export class NewPage extends BasePage {
readonly someElement: Locator;
constructor(page: Page) {
super(page);
this.someElement = page.locator('#element');
}
async performAction() {
await this.someElement.click();
}
}- Import required page classes
- Create page instances directly
- Implement step logic
- Add proper logging
Given('I am on the new page', async ({ page }) => {
const newPage = new NewPage(page);
await newPage.goto();
log.info('New page loaded');
});- Create new API client extending BaseApiClient
- Register in ServiceFactory
- Use in step definitions
- Factory handles cleanup automatically
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new features
- Ensure all tests pass
- Submit a pull request
This project is licensed under the ISC License.
- Playwright team for excellent testing framework
- Cucumber team for BDD support
- Community contributors
Built with β€οΈ by the Test Automation Team