Setting Up Your Playwright BDD Framework
Initiating a new test automation framework requires a clear plan to ensure consistency in directory structures, configuration, and execution scripts from the outset. This guide provides a step-by-step process for setting up a hybrid Behavior-Driven Development (BDD) framework powered by Playwright, Cucumber, and JavaScript.
Installation and Directory Structure
Begin by installing project dependencies and establishing the essential folder hierarchy. This includes directories for feature files, step definitions, page objects, and utility functions. A well-organized structure is critical for maintainability and scalability.
First, initialize your project and install the necessary packages. You'll need Playwright for browser automation, Cucumber.js for BDD, and potentially other utility libraries.
# Initialize npm and install dependencies
npm init -y
npm install --save-dev playwright @cucumber/cucumber javascript-yaml
Next, create the core folder structure. A common approach is to separate UI and API tests. This promotes modularity and makes it easier to manage different types of test scenarios.
# Create required folder structure
mkdir features
mkdir features/UI
mkdir features/API
mkdir step-definitions
mkdir step-definitions/UI
mkdir step-definitions/API
mkdir page-objects
mkdir utils
mkdir config
This setup provides distinct locations for your Gherkin feature files (.feature), corresponding JavaScript step definitions, reusable page objects for UI interactions, and utility functions for common tasks.
Configuring Cucumber.js
Cucumber.js needs to be configured to understand how to find your feature files and step definitions, and how to execute them. This is typically done via a cucumber.js configuration file.
Create a cucumber.js file in the root of your project. This file will specify the paths to your feature files and step definitions, as well as any other options.
const path = require('path');
module.exports = {
default: --
paths: [
'features/**/*.feature'
],
require: [
'step-definitions/**/*.js',
'utils/**/*.js'
],
requireModule: [
'node_modules/@cucumber/cucumber/world.js'
],
format: [
'summary',
'progress-bar'
],
parallel: 2 // Run tests in parallel, adjust as needed
};
This configuration tells Cucumber where to find your feature files (.feature files in the features directory and its subdirectories) and your step definitions (.js files in the step-definitions directory and its subdirectories). It also specifies the output format for test execution.
Integrating Playwright with Cucumber
To use Playwright within your Cucumber steps, you need to initialize a Playwright browser context for each scenario. This ensures a clean state for every test. You can achieve this by leveraging Cucumber's World object.
Create a custom World file (e.g., world.js in the utils directory) to manage the Playwright instance and browser context. This file will be referenced in your cucumber.js configuration.
const { setWorldConstructor, World } = require('@cucumber/cucumber');
const { chromium, firefox, webkit } = require('playwright');
class CustomWorld extends World {
constructor(options) {
super(options);
this.browser = null;
this.context = null;
this.page = null;
}
async init(browserType = chromium) {
this.browser = await browserType.launch({
headless: true // Set to false for debugging
});
this.context = await this.browser.newContext();
this.page = await this.context.newPage();
}
async close() {
if (this.context) {
await this.context.close();
this.context = null;
}
if (this.browser) {
await this.browser.close();
this.browser = null;
}
}
}
setWorldConstructor(CustomWorld);
This CustomWorld class initializes a Playwright browser, context, and page for each test run. The init method is called before each scenario, and the close method cleans up resources after each scenario. This ensures that tests are isolated and do not interfere with each other.
Writing Feature Files (Gherkin)
Feature files describe the behavior of the application in a human-readable format using Gherkin syntax. Each feature file typically contains multiple scenarios, and each scenario outlines a specific test case.
Create a file named user-login.feature inside the features/UI directory:
Feature: User Login
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter username "testuser" and password "password123"
And I click the login button
Then I should be redirected to the dashboard page
And I should see a welcome message
This feature file describes a single scenario for user login. The keywords Feature, Scenario, Given, When, And, and Then define the structure and steps of the test.
Implementing Step Definitions
Step definitions link the Gherkin steps in your feature files to actual code that performs actions and makes assertions. Each step in a feature file must have a corresponding step definition.
Create a file named login-steps.js inside the step-definitions/UI directory:
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('@playwright/test');
const LoginPage = require('../page-objects/LoginPage');
Given('I am on the login page', async function() {
this.init(); // Initialize Playwright World
await this.page.goto('http://localhost:3000/login'); // Replace with your app's URL
});
When('I enter username "{string}" and password "{string}"', async function(username, password) {
const loginPage = new LoginPage(this.page);
await loginPage.fillUsername(username);
await loginPage.fillPassword(password);
});
And('I click the login button', async function() {
const loginPage = new LoginPage(this.page);
await loginPage.clickLoginButton();
});
Then('I should be redirected to the dashboard page', async function() {
const url = await this.page.url();
expect(url).toContain('/dashboard'); // Adjust URL as per your application
});
Then('I should see a welcome message', async function() {
const welcomeMessage = await this.page.locator('text=Welcome, testuser!'); // Adjust selector
expect(welcomeMessage).toBeVisible();
});
In this example, the Given step initializes the Playwright World, navigates to the login page, and uses a LoginPage object to interact with the UI. The When and And steps use methods from the LoginPage to fill in credentials and click the button. The Then steps use Playwright locators and assertions from @playwright/test to verify the outcome.
Implementing Page Objects
Page Object Model (POM) is a design pattern that enhances maintainability by creating an object for each page of the application. This centralizes the locators and methods for interacting with elements on that page.
Create a file named LoginPage.js inside the page-objects directory:
class LoginPage {
constructor(page) {
this.page = page;
this.usernameInput = page.locator('#username'); // Adjust selector
this.passwordInput = page.locator('#password'); // Adjust selector
this.loginButton = page.locator('button[type="submit"]'); // Adjust selector
}
async fillUsername(username) {
await this.usernameInput.fill(username);
}
async fillPassword(password) {
await this.passwordInput.fill(password);
}
async clickLoginButton() {
await this.loginButton.click();
}
}
module.exports = LoginPage;
The LoginPage class encapsulates the elements and actions specific to the login page. This makes your step definitions cleaner and easier to read, as they delegate UI interactions to the page objects.
Running the Tests
With the framework set up, feature files written, step definitions implemented, and page objects created, you can now run your BDD tests. Add a script to your package.json file to execute Cucumber.
Open your package.json file and add the following script:
{
"name": "playwright-bdd-framework",
"version": "1.0.0",
"scripts": {
"test": "cucumber-js --profile default"
},
"devDependencies": {
"@cucumber/cucumber": "^9.0.0",
"playwright": "^1.30.0",
"javascript-yaml": "^4.0.0"
}
}
Now, you can run your tests from the terminal using:
npm test
This command executes Cucumber.js, which will discover your feature files and run the corresponding step definitions using Playwright. The output will show the test results, indicating whether scenarios passed or failed.
Extending the Framework
This foundational setup can be extended to include API testing, parallel execution across different browsers, custom reporting, and integration with CI/CD pipelines. For API testing, you would create separate feature files and step definitions under features/API and step-definitions/API, using Playwright's network interception capabilities or a separate HTTP client library.
Parallel execution can be configured in the cucumber.js file using the parallel option. Custom reporters can be integrated by developing custom Cucumber formatters or using existing third-party reporters.
The hybrid nature of this framework allows you to combine UI and API tests within a single, cohesive test suite, providing comprehensive coverage for your application.
