Previous: TestNG Integration Back to Courses

Module 11: Real-World Project Implementation

Overview

This module brings together all the concepts and techniques you've learned throughout the WebDriverIO course into a comprehensive real-world project. You'll build a complete test automation framework for an e-commerce application, implementing page objects, custom commands, data-driven testing, visual regression, and reporting.

Learning Objectives

By the end of this module, you will be able to:

  1. Design and implement a complete test automation framework using WebDriverIO
  2. Apply best practices for test organization, maintainability, and reliability
  3. Implement various testing types including functional, visual, and API testing
  4. Create comprehensive reporting and documentation for your test framework
  5. Deploy and run tests in a CI/CD pipeline

10.1 Project Setup and Structure

Project Requirements

Our project will create a test automation framework for an e-commerce website with the following requirements:

Framework Architecture

We'll implement a layered architecture:

e-commerce-tests/
├── config/                  # Configuration files
│   ├── wdio.conf.js         # Base WebDriverIO configuration
│   ├── wdio.local.conf.js   # Local execution configuration
│   └── wdio.ci.conf.js      # CI execution configuration
├── test/                    # Test files
│   ├── specs/               # Test specifications
│   │   ├── account/         # Account management tests
│   │   ├── product/         # Product browsing tests
│   │   └── checkout/        # Checkout process tests
│   └── visual/              # Visual regression tests
├── pages/                   # Page objects
│   ├── base.page.js         # Base page object
│   ├── home.page.js         # Home page object
│   ├── product.page.js      # Product page object
│   └── checkout.page.js     # Checkout page object
├── components/              # Reusable UI components
│   ├── header.component.js  # Header component
│   ├── footer.component.js  # Footer component
│   └── cart.component.js    # Shopping cart component
├── utils/                   # Utility functions
│   ├── selectors.js         # Selector helpers
│   ├── waits.js             # Custom wait functions
│   └── dataGenerator.js     # Test data generation
├── data/                    # Test data
│   ├── users.json           # User credentials
│   └── products.json        # Product information
├── reports/                 # Test reports
├── screenshots/             # Screenshots for failed tests
├── .eslintrc.js             # ESLint configuration
├── .gitignore               # Git ignore file
├── package.json             # Project dependencies
└── README.md                # Project documentation

Initial Setup

Let's start by setting up the project:

# Create project directory
mkdir e-commerce-tests
cd e-commerce-tests

# Initialize npm project
npm init -y

# Install WebDriverIO and dependencies
npm install @wdio/cli --save-dev
npx wdio config

# Install additional dependencies
npm install faker chai axios moment lodash --save-dev
npm install eslint eslint-plugin-wdio --save-dev

Base Configuration

Create the base WebDriverIO configuration:

// config/wdio.conf.js
const path = require('path');
const moment = require('moment');

exports.config = {
    //
    // ====================
    // Runner Configuration
    // ====================
    runner: 'local',

    //
    // ==================
    // Specify Test Files
    // ==================
    specs: [
        './test/specs/**/*.js'
    ],
    exclude: [],

    //
    // ============
    // Capabilities
    // ============
    maxInstances: 5,
    capabilities: [{
        maxInstances: 5,
        browserName: 'chrome',
        acceptInsecureCerts: true,
        'goog:chromeOptions': {
            args: ['--headless', '--disable-gpu', '--window-size=1920,1080']
        }
    }],

    //
    // ===================
    // Test Configurations
    // ===================
    logLevel: 'info',
    bail: 0,
    baseUrl: 'https://www.saucedemo.com',
    waitforTimeout: 10000,
    connectionRetryTimeout: 120000,
    connectionRetryCount: 3,

    //
    // Test framework
    framework: 'mocha',
    mochaOpts: {
        ui: 'bdd',
        timeout: 60000
    },

    //
    // =====
    // Hooks
    // =====
    before: function (capabilities, specs) {
        // Add custom commands
        require('../utils/commands');

        // Set up chai
        const chai = require('chai');
        global.expect = chai.expect;
    },

    beforeTest: function (test, context) {
        // Setup for test
    },

    afterTest: async function(test, context, { error, result, duration, passed, retries }) {
        // Take screenshot if test fails
        if (error) {
            const timestamp = moment().format('YYYYMMDD-HHmmss');
            const testName = test.title.replace(/\s+/g, '-');
            const screenshotPath = path.join(
                process.cwd(),
                'screenshots',
                `${testName}-${timestamp}.png`
            );

            await browser.saveScreenshot(screenshotPath);
            console.log(`Screenshot saved to: ${screenshotPath}`);
        }
    },

    //
    // =====
    // Reporters
    // =====
    reporters: [
        'spec',
        ['allure', {
            outputDir: './reports/allure-results',
            disableWebdriverStepsReporting: true,
            disableWebdriverScreenshotsReporting: false,
        }],
        ['junit', {
            outputDir: './reports/junit',
            outputFileFormat: function(options) {
                return `results-${options.cid}.xml`;
            }
        }]
    ],
};

Environment-Specific Configurations

Create configurations for different environments:

// config/wdio.local.conf.js
const { config } = require('./wdio.conf');

exports.config = {
    ...config,
    maxInstances: 1,
    capabilities: [{
        maxInstances: 1,
        browserName: 'chrome',
        acceptInsecureCerts: true,
        'goog:chromeOptions': {
            args: ['--window-size=1920,1080']
        }
    }],
    baseUrl: 'https://www.saucedemo.com',
    logLevel: 'info'
};
// config/wdio.ci.conf.js
const { config } = require('./wdio.conf');

exports.config = {
    ...config,
    maxInstances: 5,
    capabilities: [{
        maxInstances: 5,
        browserName: 'chrome',
        acceptInsecureCerts: true,
        'goog:chromeOptions': {
            args: ['--headless', '--disable-gpu', '--window-size=1920,1080', '--no-sandbox']
        }
    }],
    baseUrl: 'https://www.saucedemo.com',
    logLevel: 'error'
};

10.2 Page Objects and Components

Base Page Object

Create a base page object that all other page objects will extend:

// pages/base.page.js
class BasePage {
    constructor() {
        this.url = '/';
    }

    async open() {
        await browser.url(this.url);
    }

    async waitForPageLoad() {
        await browser.waitUntil(
            async () => await browser.execute(() => document.readyState === 'complete'),
            {
                timeout: 30000,
                timeoutMsg: 'Page did not finish loading'
            }
        );
    }

    async getTitle() {
        return browser.getTitle();
    }

    async getUrl() {
        return browser.getUrl();
    }

    async scrollToElement(element) {
        await element.scrollIntoView();
    }

    async waitForElementDisplayed(element, timeout = 10000) {
        await element.waitForDisplayed({ timeout });
    }

    async waitForElementClickable(element, timeout = 10000) {
        await element.waitForClickable({ timeout });
    }

    async waitForElementExist(element, timeout = 10000) {
        await element.waitForExist({ timeout });
    }
}

module.exports = BasePage;

Header Component

Create a reusable header component:

// components/header.component.js
class HeaderComponent {
    constructor() {
        this.logo = $('.app_logo');
        this.shoppingCart = $('.shopping_cart_link');
        this.burgerMenu = $('#react-burger-menu-btn');
        this.logoutLink = $('#logout_sidebar_link');
        this.resetAppLink = $('#reset_sidebar_link');
        this.allItemsLink = $('#inventory_sidebar_link');
    }

    async getCartItemCount() {
        const badge = await $('.shopping_cart_badge');
        if (await badge.isExisting()) {
            return parseInt(await badge.getText());
        }
        return 0;
    }

    async openCart() {
        await this.shoppingCart.click();
    }

    async openMenu() {
        await this.burgerMenu.click();
        // Wait for menu to open
        await $('#logout_sidebar_link').waitForDisplayed({ timeout: 5000 });
    }

    async logout() {
        await this.openMenu();
        await this.logoutLink.click();
    }

    async resetApp() {
        await this.openMenu();
        await this.resetAppLink.click();
    }

    async goToAllItems() {
        await this.openMenu();
        await this.allItemsLink.click();
    }
}

module.exports = HeaderComponent;

Login Page Object

Create a page object for the login page:

// pages/login.page.js
const BasePage = require('./base.page');

class LoginPage extends BasePage {
    constructor() {
        super();
        this.url = '/';
        this.usernameInput = $('#user-name');
        this.passwordInput = $('#password');
        this.loginButton = $('#login-button');
        this.errorMessage = $('.error-message-container');
    }

    async login(username, password) {
        await this.usernameInput.setValue(username);
        await this.passwordInput.setValue(password);
        await this.loginButton.click();
    }

    async getErrorMessage() {
        if (await this.errorMessage.isDisplayed()) {
            return this.errorMessage.getText();
        }
        return null;
    }

    async isErrorDisplayed() {
        return this.errorMessage.isDisplayed();
    }
}

module.exports = LoginPage;

Products Page Object

Create a page object for the products page:

// pages/products.page.js
const BasePage = require('./base.page');
const HeaderComponent = require('../components/header.component');

class ProductsPage extends BasePage {
    constructor() {
        super();
        this.url = '/inventory.html';
        this.header = new HeaderComponent();
        this.productItems = $('.inventory_item');
        this.productTitles = $('.inventory_item_name');
        this.productPrices = $('.inventory_item_price');
        this.addToCartButtons = $('.btn_inventory');
        this.sortDropdown = $('.product_sort_container');
    }

    async getProductCount() {
        return (await $$('.inventory_item')).length;
    }

    async getProductTitle(index) {
        const titles = await $$('.inventory_item_name');
        if (index < titles.length) {
            return titles[index].getText();
        }
        throw new Error(`Product index ${index} out of bounds`);
    }

    async getProductPrice(index) {
        const prices = await $$('.inventory_item_price');
        if (index < prices.length) {
            const priceText = await prices[index].getText();
            return parseFloat(priceText.replace('$', ''));
        }
        throw new Error(`Product index ${index} out of bounds`);
    }

    async addProductToCart(index) {
        const buttons = await $$('.btn_inventory');
        if (index < buttons.length) {
            await buttons[index].click();
        } else {
            throw new Error(`Product index ${index} out of bounds`);
        }
    }

    async openProductDetails(index) {
        const titles = await $$('.inventory_item_name');
        if (index < titles.length) {
            await titles[index].click();
        } else {
            throw new Error(`Product index ${index} out of bounds`);
        }
    }

    async sortProducts(option) {
        await this.sortDropdown.click();
        await $(`option[value="${option}"]`).click();
    }

    async getAllProductTitles() {
        const titles = await $$('.inventory_item_name');
        return Promise.all(titles.map(title => title.getText()));
    }

    async getAllProductPrices() {
        const prices = await $$('.inventory_item_price');
        return Promise.all(prices.map(async (price) => {
            const priceText = await price.getText();
            return parseFloat(priceText.replace('$', ''));
        }));
    }
}

module.exports = ProductsPage;

Product Details Page Object

Create a page object for the product details page:

// pages/product-details.page.js
const BasePage = require('./base.page');
const HeaderComponent = require('../components/header.component');

class ProductDetailsPage extends BasePage {
    constructor() {
        super();
        this.header = new HeaderComponent();
        this.productTitle = $('.inventory_details_name');
        this.productDescription = $('.inventory_details_desc');
        this.productPrice = $('.inventory_details_price');
        this.addToCartButton = $('.btn_inventory');
        this.backButton = $('#back-to-products');
    }

    async getProductTitle() {
        return this.productTitle.getText();
    }

    async getProductDescription() {
        return this.productDescription.getText();
    }

    async getProductPrice() {
        const priceText = await this.productPrice.getText();
        return parseFloat(priceText.replace('$', ''));
    }

    async addToCart() {
        await this.addToCartButton.click();
    }

    async goBack() {
        await this.backButton.click();
    }

    async isAddToCartButtonDisplayed() {
        return this.addToCartButton.isDisplayed();
    }
}

module.exports = ProductDetailsPage;

Cart Page Object

Create a page object for the shopping cart page:

// pages/cart.page.js
const BasePage = require('./base.page');
const HeaderComponent = require('../components/header.component');

class CartPage extends BasePage {
    constructor() {
        super();
        this.url = '/cart.html';
        this.header = new HeaderComponent();
        this.cartItems = $('.cart_item');
        this.checkoutButton = $('#checkout');
        this.continueShoppingButton = $('#continue-shopping');
        this.removeButtons = $('.cart_button');
    }

    async getCartItemCount() {
        return (await $$('.cart_item')).length;
    }

    async getCartItemTitle(index) {
        const titles = await $$('.inventory_item_name');
        if (index < titles.length) {
            return titles[index].getText();
        }
        throw new Error(`Cart item index ${index} out of bounds`);
    }

    async getCartItemPrice(index) {
        const prices = await $$('.inventory_item_price');
        if (index < prices.length) {
            const priceText = await prices[index].getText();
            return parseFloat(priceText.replace('$', ''));
        }
        throw new Error(`Cart item index ${index} out of bounds`);
    }

    async removeCartItem(index) {
        const buttons = await $$('.cart_button');
        if (index < buttons.length) {
            await buttons[index].click();
        } else {
            throw new Error(`Cart item index ${index} out of bounds`);
        }
    }

    async proceedToCheckout() {
        await this.checkoutButton.click();
    }

    async continueShopping() {
        await this.continueShoppingButton.click();
    }

    async getAllCartItemTitles() {
        const titles = await $$('.inventory_item_name');
        return Promise.all(titles.map(title => title.getText()));
    }

    async getTotalPrice() {
        const prices = await $$('.inventory_item_price');
        const priceValues = await Promise.all(prices.map(async (price) => {
            const priceText = await price.getText();
            return parseFloat(priceText.replace('$', ''));
        }));

        return priceValues.reduce((total, price) => total + price, 0);
    }
}

module.exports = CartPage;

Checkout Pages

Create page objects for the checkout process:

// pages/checkout-step-one.page.js
const BasePage = require('./base.page');
const HeaderComponent = require('../components/header.component');

class CheckoutStepOnePage extends BasePage {
    constructor() {
        super();
        this.url = '/checkout-step-one.html';
        this.header = new HeaderComponent();
        this.firstNameInput = $('#first-name');
        this.lastNameInput = $('#last-name');
        this.postalCodeInput = $('#postal-code');
        this.continueButton = $('#continue');
        this.cancelButton = $('#cancel');
        this.errorMessage = $('.error-message-container');
    }

    async fillShippingInfo(firstName, lastName, postalCode) {
        await this.firstNameInput.setValue(firstName);
        await this.lastNameInput.setValue(lastName);
        await this.postalCodeInput.setValue(postalCode);
    }

    async continue() {
        await this.continueButton.click();
    }

    async cancel() {
        await this.cancelButton.click();
    }

    async getErrorMessage() {
        if (await this.errorMessage.isDisplayed()) {
            return this.errorMessage.getText();
        }
        return null;
    }
}

module.exports = CheckoutStepOnePage;
// pages/checkout-step-two.page.js
const BasePage = require('./base.page');
const HeaderComponent = require('../components/header.component');

class CheckoutStepTwoPage extends BasePage {
    constructor() {
        super();
        this.url = '/checkout-step-two.html';
        this.header = new HeaderComponent();
        this.cartItems = $('.cart_item');
        this.finishButton = $('#finish');
        this.cancelButton = $('#cancel');
        this.subtotalLabel = $('.summary_subtotal_label');
        this.taxLabel = $('.summary_tax_label');
        this.totalLabel = $('.summary_total_label');
    }

    async getCartItemCount() {
        return (await $$('.cart_item')).length;
    }

    async getSubtotal() {
        const subtotalText = await this.subtotalLabel.getText();
        return parseFloat(subtotalText.replace('Item total: $', ''));
    }

    async getTax() {
        const taxText = await this.taxLabel.getText();
        return parseFloat(taxText.replace('Tax: $', ''));
    }

    async getTotal() {
        const totalText = await this.totalLabel.getText();
        return parseFloat(totalText.replace('Total: $', ''));
    }

    async finish() {
        await this.finishButton.click();
    }

    async cancel() {
        await this.cancelButton.click();
    }
}

module.exports = CheckoutStepTwoPage;
// pages/checkout-complete.page.js
const BasePage = require('./base.page');
const HeaderComponent = require('../components/header.component');

class CheckoutCompletePage extends BasePage {
    constructor() {
        super();
        this.url = '/checkout-complete.html';
        this.header = new HeaderComponent();
        this.thankYouHeader = $('.complete-header');
        this.completeText = $('.complete-text');
        this.backHomeButton = $('#back-to-products');
    }

    async getThankYouMessage() {
        return this.thankYouHeader.getText();
    }

    async getCompleteText() {
        return this.completeText.getText();
    }

    async backToHome() {
        await this.backHomeButton.click();
    }

    async isOrderCompleted() {
        return this.thankYouHeader.isDisplayed();
    }
}

module.exports = CheckoutCompletePage;

10.3 Utility Functions and Custom Commands

Custom Commands

Create custom commands to extend WebDriverIO functionality:

// utils/commands.js
browser.addCommand('waitForPageLoad', async function() {
    await browser.waitUntil(
        async () => await browser.execute(() => document.readyState === 'complete'),
        {
            timeout: 30000,
            timeoutMsg: 'Page did not finish loading'
        }
    );
});

browser.addCommand('waitForUrlContains', async function(fragment, timeout = 10000) {
    await browser.waitUntil(
        async () => {
            const url = await browser.getUrl();
            return url.includes(fragment);
        },
        {
            timeout,
            timeoutMsg: `URL did not contain "${fragment}" within ${timeout}ms`
        }
    );
});

browser.addCommand('waitForTextContains', async function(selector, text, timeout = 10000) {
    const element = await $(selector);
    await browser.waitUntil(
        async () => {
            const elementText = await element.getText();
            return elementText.includes(text);
        },
        {
            timeout,
            timeoutMsg: `Element "${selector}" text did not contain "${text}" within ${timeout}ms`
        }
    );
});

browser.addCommand('login', async function(username, password) {
    await browser.url('/');
    await $('#user-name').setValue(username);
    await $('#password').setValue(password);
    await $('#login-button').click();
    await browser.waitForUrlContains('/inventory.html');
});

browser.addCommand('logout', async function() {
    await $('#react-burger-menu-btn').click();
    await $('#logout_sidebar_link').waitForDisplayed({ timeout: 5000 });
    await $('#logout_sidebar_link').click();
    await browser.waitForUrlContains('/');
});

browser.addCommand('resetApp', async function() {
    await $('#react-burger-menu-btn').click();
    await $('#reset_sidebar_link').waitForDisplayed({ timeout: 5000 });
    await $('#reset_sidebar_link').click();
});

browser.addCommand('addProductToCart', async function(index) {
    const buttons = await $$('.btn_inventory');
    if (index < buttons.length) {
        await buttons[index].click();
    } else {
        throw new Error(`Product index ${index} out of bounds`);
    }
});

browser.addCommand('getCartCount', async function() {
    const badge = await $('.shopping_cart_badge');
    if (await badge.isExisting()) {
        return parseInt(await badge.getText());
    }
    return 0;
});

Test Data Generator

Create a utility for generating test data:

// utils/dataGenerator.js
const faker = require('faker');

class DataGenerator {
    static generateUser() {
        return {
            firstName: faker.name.firstName(),
            lastName: faker.name.lastName(),
            postalCode: faker.address.zipCode()
        };
    }

    static generateCreditCard() {
        return {
            number: faker.finance.creditCardNumber(),
            expiry: `${faker.datatype.number({ min: 1, max: 12 })}/25`,
            cvv: faker.finance.creditCardCVV()
        };
    }

    static generateAddress() {
        return {
            street: faker.address.streetAddress(),
            city: faker.address.city(),
            state: faker.address.state(),
            zipCode: faker.address.zipCode()
        };
    }
}

module.exports = DataGenerator;

Wait Utilities

Create custom wait functions:

// utils/waits.js
class WaitUtils {
    static async forElementDisplayed(selector, timeout = 10000) {
        const element = await $(selector);
        await element.waitForDisplayed({ timeout });
        return element;
    }

    static async forElementClickable(selector, timeout = 10000) {
        const element = await $(selector);
        await element.waitForClickable({ timeout });
        return element;
    }

    static async forElementExist(selector, timeout = 10000) {
        const element = await $(selector);
        await element.waitForExist({ timeout });
        return element;
    }

    static async forTextContains(selector, text, timeout = 10000) {
        const element = await $(selector);
        await browser.waitUntil(
            async () => {
                const elementText = await element.getText();
                return elementText.includes(text);
            },
            {
                timeout,
                timeoutMsg: `Element "${selector}" text did not contain "${text}" within ${timeout}ms`
            }
        );
        return element;
    }

    static async forUrlContains(fragment, timeout = 10000) {
        await browser.waitUntil(
            async () => {
                const url = await browser.getUrl();
                return url.includes(fragment);
            },
            {
                timeout,
                timeoutMsg: `URL did not contain "${fragment}" within ${timeout}ms`
            }
        );
    }

    static async forElementCount(selector, count, timeout = 10000) {
        await browser.waitUntil(
            async () => {
                const elements = await $$(selector);
                return elements.length === count;
            },
            {
                timeout,
                timeoutMsg: `Element count for "${selector}" did not equal ${count} within ${timeout}ms`
            }
        );
    }
}

module.exports = WaitUtils;

10.4 Test Implementation

1. Login Tests

Create tests for the login functionality:

// test/specs/account/login.spec.js
const LoginPage = require('../../../pages/login.page');
const ProductsPage = require('../../../pages/products.page');

describe('Login Functionality', () => {
    let loginPage;
    let productsPage;

    beforeEach(async () => {
        loginPage = new LoginPage();
        productsPage = new ProductsPage();
        await loginPage.open();
    });

    it('should login with valid credentials', async () => {
        await loginPage.login('standard_user', 'secret_sauce');

        // Verify redirect to products page
        const url = await browser.getUrl();
        expect(url).to.include('/inventory.html');

        // Verify products are displayed
        const productCount = await productsPage.getProductCount();
        expect(productCount).to.be.greaterThan(0);
    });

    it('should show error with invalid credentials', async () => {
        await loginPage.login('invalid_user', 'invalid_password');

        // Verify error message
        const errorMessage = await loginPage.getErrorMessage();
        expect(errorMessage).to.include('Username and password do not match');

        // Verify still on login page
        const url = await browser.getUrl();
        expect(url).not.to.include('/inventory.html');
    });

    it('should show error with locked out user', async () => {
        await loginPage.login('locked_out_user', 'secret_sauce');

        // Verify error message
        const errorMessage = await loginPage.getErrorMessage();
        expect(errorMessage).to.include('locked out');

        // Verify still on login page
        const url = await browser.getUrl();
        expect(url).not.to.include('/inventory.html');
    });

    it('should login and logout successfully', async () => {
        await loginPage.login('standard_user', 'secret_sauce');

        // Verify login successful
        const url = await browser.getUrl();
        expect(url).to.include('/inventory.html');

        // Logout
        await browser.logout();

        // Verify back on login page
        const loginUrl = await browser.getUrl();
        expect(loginUrl).not.to.include('/inventory.html');
        expect(await loginPage.usernameInput.isDisplayed()).to.be.true;
    });
});

2. Product Browsing Tests

Create tests for product browsing functionality:

// test/specs/product/product-browsing.spec.js
const LoginPage = require('../../../pages/login.page');
const ProductsPage = require('../../../pages/products.page');
const ProductDetailsPage = require('../../../pages/product-details.page');

describe('Product Browsing', () => {
    let productsPage;
    let productDetailsPage;

    before(async () => {
        // Login before all tests
        await browser.login('standard_user', 'secret_sauce');
    });

    beforeEach(async () => {
        productsPage = new ProductsPage();
        productDetailsPage = new ProductDetailsPage();
        await productsPage.open();
    });

    it('should display correct number of products', async () => {
        const productCount = await productsPage.getProductCount();
        expect(productCount).to.equal(6);
    });

    it('should sort products by price low to high', async () => {
        await productsPage.sortProducts('lohi');

        const prices = await productsPage.getAllProductPrices();

        // Verify prices are in ascending order
        for (let i = 0; i < prices.length - 1; i++) {
            expect(prices[i]).to.be.at.most(prices[i + 1]);
        }
    });

    it('should sort products by price high to low', async () => {
        await productsPage.sortProducts('hilo');

        const prices = await productsPage.getAllProductPrices();

        // Verify prices are in descending order
        for (let i = 0; i < prices.length - 1; i++) {
            expect(prices[i]).to.be.at.least(prices[i + 1]);
        }
    });

    it('should sort products alphabetically', async () => {
        await productsPage.sortProducts('az');

        const titles = await productsPage.getAllProductTitles();

        // Verify titles are in alphabetical order
        const sortedTitles = [...titles].sort();
        expect(titles).to.deep.equal(sortedTitles);
    });

    it('should sort products reverse alphabetically', async () => {
        await productsPage.sortProducts('za');

        const titles = await productsPage.getAllProductTitles();

        // Verify titles are in reverse alphabetical order
        const sortedTitles = [...titles].sort().reverse();
        expect(titles).to.deep.equal(sortedTitles);
    });

    it('should open product details page', async () => {
        const productTitle = await productsPage.getProductTitle(0);
        await productsPage.openProductDetails(0);

        // Verify on product details page
        const detailsTitle = await productDetailsPage.getProductTitle();
        expect(detailsTitle).to.equal(productTitle);
    });

    it('should add product to cart from products page', async () => {
        // Get initial cart count
        const initialCartCount = await browser.getCartCount();

        // Add product to cart
        await productsPage.addProductToCart(0);

        // Verify cart count increased
        const newCartCount = await browser.getCartCount();
        expect(newCartCount).to.equal(initialCartCount + 1);
    });

    it('should add product to cart from details page', async () => {
        // Open product details
        await productsPage.openProductDetails(1);

        // Get initial cart count
        const initialCartCount = await browser.getCartCount();

        // Add product to cart
        await productDetailsPage.addToCart();

        // Verify cart count increased
        const newCartCount = await browser.getCartCount();
        expect(newCartCount).to.equal(initialCartCount + 1);
    });

    after(async () => {
        // Reset app state after all tests
        await browser.resetApp();
    });
});

3. Shopping Cart Tests

Create tests for the shopping cart functionality:

// test/specs/product/cart.spec.js
const ProductsPage = require('../../../pages/products.page');
const CartPage = require('../../../pages/cart.page');

describe('Shopping Cart', () => {
    let productsPage;
    let cartPage;

    before(async () => {
        // Login before all tests
        await browser.login('standard_user', 'secret_sauce');
    });

    beforeEach(async () => {
        productsPage = new ProductsPage();
        cartPage = new CartPage();

        // Reset app state before each test
        await browser.resetApp();
        await productsPage.open();
    });

    it('should add products to cart', async () => {
        // Add two products to cart
        await productsPage.addProductToCart(0);
        await productsPage.addProductToCart(1);

        // Verify cart count
        const cartCount = await browser.getCartCount();
        expect(cartCount).to.equal(2);

        // Open cart
        await productsPage.header.openCart();

        // Verify cart items
        const cartItemCount = await cartPage.getCartItemCount();
        expect(cartItemCount).to.equal(2);
    });

    it('should remove products from cart', async () => {
        // Add two products to cart
        await productsPage.addProductToCart(0);
        await productsPage.addProductToCart(1);

        // Open cart
        await productsPage.header.openCart();

        // Verify initial cart items
        const initialCartItemCount = await cartPage.getCartItemCount();
        expect(initialCartItemCount).to.equal(2);

        // Remove one item
        await cartPage.removeCartItem(0);

        // Verify cart count updated
        const updatedCartItemCount = await cartPage.getCartItemCount();
        expect(updatedCartItemCount).to.equal(1);
    });

    it('should continue shopping from cart', async () => {
        // Add product to cart
        await productsPage.addProductToCart(0);

        // Open cart
        await productsPage.header.openCart();

        // Continue shopping
        await cartPage.continueShopping();

        // Verify back on products page
        const url = await browser.getUrl();
        expect(url).to.include('/inventory.html');
    });

    it('should calculate correct total price', async () => {
        // Add two products to cart
        await productsPage.addProductToCart(0);
        await productsPage.addProductToCart(1);

        // Get product prices
        const price1 = await productsPage.getProductPrice(0);
        const price2 = await productsPage.getProductPrice(1);
        const expectedTotal = price1 + price2;

        // Open cart
        await productsPage.header.openCart();

        // Get total price
        const totalPrice = await cartPage.getTotalPrice();

        // Verify total price
        expect(totalPrice).to.equal(expectedTotal);
    });

    after(async () => {
        // Reset app state after all tests
        await browser.resetApp();
    });
});

4. Checkout Process Tests

Create tests for the checkout process:

// test/specs/checkout/checkout-process.spec.js
const ProductsPage = require('../../../pages/products.page');
const CartPage = require('../../../pages/cart.page');
const CheckoutStepOnePage = require('../../../pages/checkout-step-one.page');
const CheckoutStepTwoPage = require('../../../pages/checkout-step-two.page');
const CheckoutCompletePage = require('../../../pages/checkout-complete.page');
const DataGenerator = require('../../../utils/dataGenerator');

describe('Checkout Process', () => {
    let productsPage;
    let cartPage;
    let checkoutStepOnePage;
    let checkoutStepTwoPage;
    let checkoutCompletePage;

    before(async () => {
        // Login before all tests
        await browser.login('standard_user', 'secret_sauce');
    });

    beforeEach(async () => {
        productsPage = new ProductsPage();
        cartPage = new CartPage();
        checkoutStepOnePage = new CheckoutStepOnePage();
        checkoutStepTwoPage = new CheckoutStepTwoPage();
        checkoutCompletePage = new CheckoutCompletePage();

        // Reset app state before each test
        await browser.resetApp();
        await productsPage.open();

        // Add products to cart
        await productsPage.addProductToCart(0);
        await productsPage.addProductToCart(1);

        // Go to cart
        await productsPage.header.openCart();
    });

    it('should complete checkout process with valid information', async () => {
        // Proceed to checkout
        await cartPage.proceedToCheckout();

        // Generate user data
        const userData = DataGenerator.generateUser();

        // Fill shipping information
        await checkoutStepOnePage.fillShippingInfo(
            userData.firstName,
            userData.lastName,
            userData.postalCode
        );
        await checkoutStepOnePage.continue();

        // Verify on checkout step two
        const stepTwoUrl = await browser.getUrl();
        expect(stepTwoUrl).to.include('/checkout-step-two.html');

        // Verify cart items
        const cartItemCount = await checkoutStepTwoPage.getCartItemCount();
        expect(cartItemCount).to.equal(2);

        // Verify totals
        const subtotal = await checkoutStepTwoPage.getSubtotal();
        const tax = await checkoutStepTwoPage.getTax();
        const total = await checkoutStepTwoPage.getTotal();

        expect(total).to.equal(subtotal + tax);

        // Complete checkout
        await checkoutStepTwoPage.finish();

        // Verify on checkout complete page
        const completeUrl = await browser.getUrl();
        expect(completeUrl).to.include('/checkout-complete.html');

        // Verify thank you message
        const thankYouMessage = await checkoutCompletePage.getThankYouMessage();
        expect(thankYouMessage).to.include('THANK YOU');

        // Verify order completed
        const isOrderCompleted = await checkoutCompletePage.isOrderCompleted();
        expect(isOrderCompleted).to.be.true;
    });

    it('should show error with missing information', async () => {
        // Proceed to checkout
        await cartPage.proceedToCheckout();

        // Submit without filling information
        await checkoutStepOnePage.continue();

        // Verify error message
        const errorMessage = await checkoutStepOnePage.getErrorMessage();
        expect(errorMessage).to.include('First Name is required');

        // Fill only first name
        await checkoutStepOnePage.fillShippingInfo('John', '', '');
        await checkoutStepOnePage.continue();

        // Verify error message
        const lastNameError = await checkoutStepOnePage.getErrorMessage();
        expect(lastNameError).to.include('Last Name is required');

        // Fill first and last name
        await checkoutStepOnePage.fillShippingInfo('John', 'Doe', '');
        await checkoutStepOnePage.continue();

        // Verify error message
        const postalCodeError = await checkoutStepOnePage.getErrorMessage();
        expect(postalCodeError).to.include('Postal Code is required');
    });

    it('should cancel checkout and return to cart', async () => {
        // Proceed to checkout
        await cartPage.proceedToCheckout();

        // Cancel checkout
        await checkoutStepOnePage.cancel();

        // Verify back on cart page
        const cartUrl = await browser.getUrl();
        expect(cartUrl).to.include('/cart.html');
    });

    it('should cancel checkout at review step', async () => {
        // Proceed to checkout
        await cartPage.proceedToCheckout();

        // Generate user data
        const userData = DataGenerator.generateUser();

        // Fill shipping information
        await checkoutStepOnePage.fillShippingInfo(
            userData.firstName,
            userData.lastName,
            userData.postalCode
        );
        await checkoutStepOnePage.continue();

        // Cancel at review step
        await checkoutStepTwoPage.cancel();

        // Verify back on products page
        const productsUrl = await browser.getUrl();
        expect(productsUrl).to.include('/inventory.html');
    });

    it('should navigate back to home after order completion', async () => {
        // Proceed to checkout
        await cartPage.proceedToCheckout();

        // Generate user data
        const userData = DataGenerator.generateUser();

        // Fill shipping information
        await checkoutStepOnePage.fillShippingInfo(
            userData.firstName,
            userData.lastName,
            userData.postalCode
        );
        await checkoutStepOnePage.continue();

        // Complete checkout
        await checkoutStepTwoPage.finish();

        // Back to home
        await checkoutCompletePage.backToHome();

        // Verify back on products page
        const productsUrl = await browser.getUrl();
        expect(productsUrl).to.include('/inventory.html');

        // Verify cart is empty
        const cartCount = await browser.getCartCount();
        expect(cartCount).to.equal(0);
    });

    after(async () => {
        // Reset app state after all tests
        await browser.resetApp();
    });
});

5. End-to-End Tests

Create end-to-end tests that cover complete user journeys:

// test/specs/e2e/complete-purchase.spec.js
const LoginPage = require('../../../pages/login.page');
const ProductsPage = require('../../../pages/products.page');
const ProductDetailsPage = require('../../../pages/product-details.page');
const CartPage = require('../../../pages/cart.page');
const CheckoutStepOnePage = require('../../../pages/checkout-step-one.page');
const CheckoutStepTwoPage = require('../../../pages/checkout-step-two.page');
const CheckoutCompletePage = require('../../../pages/checkout-complete.page');
const DataGenerator = require('../../../utils/dataGenerator');

describe('End-to-End Purchase Flow', () => {
    let loginPage;
    let productsPage;
    let productDetailsPage;
    let cartPage;
    let checkoutStepOnePage;
    let checkoutStepTwoPage;
    let checkoutCompletePage;

    beforeEach(async () => {
        loginPage = new LoginPage();
        productsPage = new ProductsPage();
        productDetailsPage = new ProductDetailsPage();
        cartPage = new CartPage();
        checkoutStepOnePage = new CheckoutStepOnePage();
        checkoutStepTwoPage = new CheckoutStepTwoPage();
        checkoutCompletePage = new CheckoutCompletePage();

        // Start from login page
        await loginPage.open();
    });

    it('should complete purchase flow from product listing', async () => {
        // Step 1: Login
        await loginPage.login('standard_user', 'secret_sauce');

        // Step 2: Add products to cart
        await productsPage.addProductToCart(0);
        await productsPage.addProductToCart(1);

        // Step 3: Go to cart
        await productsPage.header.openCart();

        // Step 4: Proceed to checkout
        await cartPage.proceedToCheckout();

        // Step 5: Fill shipping information
        const userData = DataGenerator.generateUser();
        await checkoutStepOnePage.fillShippingInfo(
            userData.firstName,
            userData.lastName,
            userData.postalCode
        );
        await checkoutStepOnePage.continue();

        // Step 6: Review and complete order
        await checkoutStepTwoPage.finish();

        // Step 7: Verify order completion
        const thankYouMessage = await checkoutCompletePage.getThankYouMessage();
        expect(thankYouMessage).to.include('THANK YOU');

        // Step 8: Return to home
        await checkoutCompletePage.backToHome();

        // Verify back on products page with empty cart
        const productsUrl = await browser.getUrl();
        expect(productsUrl).to.include('/inventory.html');

        const cartCount = await browser.getCartCount();
        expect(cartCount).to.equal(0);
    });

    it('should complete purchase flow from product details', async () => {
        // Step 1: Login
        await loginPage.login('standard_user', 'secret_sauce');

        // Step 2: Open product details
        await productsPage.openProductDetails(0);

        // Step 3: Add product to cart from details page
        await productDetailsPage.addToCart();

        // Step 4: Go back to products
        await productDetailsPage.goBack();

        // Step 5: Open another product details
        await productsPage.openProductDetails(1);

        // Step 6: Add second product to cart
        await productDetailsPage.addToCart();

        // Step 7: Go to cart
        await productDetailsPage.header.openCart();

        // Step 8: Proceed to checkout
        await cartPage.proceedToCheckout();

        // Step 9: Fill shipping information
        const userData = DataGenerator.generateUser();
        await checkoutStepOnePage.fillShippingInfo(
            userData.firstName,
            userData.lastName,
            userData.postalCode
        );
        await checkoutStepOnePage.continue();

        // Step 10: Review and complete order
        await checkoutStepTwoPage.finish();

        // Step 11: Verify order completion
        const thankYouMessage = await checkoutCompletePage.getThankYouMessage();
        expect(thankYouMessage).to.include('THANK YOU');
    });

    after(async () => {
        // Reset app state after all tests
        await browser.resetApp();
    });
});

10.5 Visual Regression Testing

Implement visual regression tests using WebDriverIO's image comparison service:

// test/visual/visual.spec.js
const LoginPage = require('../../pages/login.page');
const ProductsPage = require('../../pages/products.page');
const ProductDetailsPage = require('../../pages/product-details.page');
const CartPage = require('../../pages/cart.page');

describe('Visual Regression Tests', () => {
    let loginPage;
    let productsPage;
    let productDetailsPage;
    let cartPage;

    before(async () => {
        loginPage = new LoginPage();
        productsPage = new ProductsPage();
        productDetailsPage = new ProductDetailsPage();
        cartPage = new CartPage();
    });

    it('should match login page baseline', async () => {
        await loginPage.open();

        // Take screenshot of entire page
        const result = await browser.checkFullPageScreen('login-page');
        expect(result).toEqual(0);
    });

    it('should match products page baseline', async () => {
        await browser.login('standard_user', 'secret_sauce');

        // Take screenshot of entire page
        const result = await browser.checkFullPageScreen('products-page');
        expect(result).toEqual(0);
    });

    it('should match product details baseline', async () => {
        await browser.login('standard_user', 'secret_sauce');
        await productsPage.openProductDetails(0);

        // Take screenshot of entire page
        const result = await browser.checkFullPageScreen('product-details-page');
        expect(result).toEqual(0);
    });

    it('should match cart page baseline', async () => {
        await browser.login('standard_user', 'secret_sauce');
        await productsPage.addProductToCart(0);
        await productsPage.header.openCart();

        // Take screenshot of entire page
        const result = await browser.checkFullPageScreen('cart-page');
        expect(result).toEqual(0);
    });

    it('should match specific components', async () => {
        await browser.login('standard_user', 'secret_sauce');

        // Check header component
        const headerResult = await browser.checkElement('.primary_header', 'header-component');
        expect(headerResult).toEqual(0);

        // Check product card
        const productCardResult = await browser.checkElement('.inventory_item:nth-child(1)', 'product-card');
        expect(productCardResult).toEqual(0);

        // Check footer
        const footerResult = await browser.checkElement('.footer', 'footer-component');
        expect(footerResult).toEqual(0);
    });

    after(async () => {
        // Reset app state after all tests
        await browser.resetApp();
    });
});

10.6 API Testing Integration

Implement API tests to complement UI tests:

// utils/apiClient.js
const axios = require('axios');

class ApiClient {
    constructor(baseUrl = 'https://www.saucedemo.com/api') {
        this.baseUrl = baseUrl;
        this.token = null;
    }

    async login(username, password) {
        try {
            const response = await axios.post(`${this.baseUrl}/login`, {
                username,
                password
            });

            this.token = response.data.token;
            return response.data;
        } catch (error) {
            console.error('Login API error:', error.message);
            throw error;
        }
    }

    async getProducts() {
        try {
            const response = await axios.get(`${this.baseUrl}/products`, {
                headers: this.getAuthHeader()
            });

            return response.data;
        } catch (error) {
            console.error('Get products API error:', error.message);
            throw error;
        }
    }

    async getProductDetails(productId) {
        try {
            const response = await axios.get(`${this.baseUrl}/products/${productId}`, {
                headers: this.getAuthHeader()
            });

            return response.data;
        } catch (error) {
            console.error('Get product details API error:', error.message);
            throw error;
        }
    }

    async addToCart(productId) {
        try {
            const response = await axios.post(`${this.baseUrl}/cart`, {
                productId
            }, {
                headers: this.getAuthHeader()
            });

            return response.data;
        } catch (error) {
            console.error('Add to cart API error:', error.message);
            throw error;
        }
    }

    async getCart() {
        try {
            const response = await axios.get(`${this.baseUrl}/cart`, {
                headers: this.getAuthHeader()
            });

            return response.data;
        } catch (error) {
            console.error('Get cart API error:', error.message);
            throw error;
        }
    }

    async checkout(checkoutInfo) {
        try {
            const response = await axios.post(`${this.baseUrl}/checkout`, checkoutInfo, {
                headers: this.getAuthHeader()
            });

            return response.data;
        } catch (error) {
            console.error('Checkout API error:', error.message);
            throw error;
        }
    }

    getAuthHeader() {
        return this.token ? { Authorization: `Bearer ${this.token}` } : {};
    }
}

module.exports = ApiClient;
// test/specs/api/api.spec.js
const ApiClient = require('../../../utils/apiClient');
const DataGenerator = require('../../../utils/dataGenerator');

describe('API Tests', () => {
    let apiClient;

    before(async () => {
        apiClient = new ApiClient();

        // Note: In a real implementation, you would use actual API endpoints
        // This is a mock implementation for demonstration purposes
    });

    it('should login via API', async () => {
        try {
            const loginResponse = await apiClient.login('standard_user', 'secret_sauce');
            expect(loginResponse.token).to.exist;
        } catch (error) {
            // Skip test if API is not available
            console.log('API not available, skipping test');
            this.skip();
        }
    });

    it('should get products via API', async () => {
        try {
            await apiClient.login('standard_user', 'secret_sauce');
            const products = await apiClient.getProducts();

            expect(products).to.be.an('array');
            expect(products.length).to.be.greaterThan(0);

            // Verify product structure
            const firstProduct = products[0];
            expect(firstProduct).to.have.property('id');
            expect(firstProduct).to.have.property('name');
            expect(firstProduct).to.have.property('price');
        } catch (error) {
            // Skip test if API is not available
            console.log('API not available, skipping test');
            this.skip();
        }
    });

    it('should add product to cart via API', async () => {
        try {
            await apiClient.login('standard_user', 'secret_sauce');
            const products = await apiClient.getProducts();

            // Add first product to cart
            const addToCartResponse = await apiClient.addToCart(products[0].id);
            expect(addToCartResponse.success).to.be.true;

            // Get cart and verify product was added
            const cart = await apiClient.getCart();
            expect(cart.items).to.be.an('array');
            expect(cart.items.length).to.equal(1);
            expect(cart.items[0].id).to.equal(products[0].id);
        } catch (error) {
            // Skip test if API is not available
            console.log('API not available, skipping test');
            this.skip();
        }
    });

    it('should complete checkout via API', async () => {
        try {
            await apiClient.login('standard_user', 'secret_sauce');
            const products = await apiClient.getProducts();

            // Add product to cart
            await apiClient.addToCart(products[0].id);

            // Generate checkout data
            const userData = DataGenerator.generateUser();

            // Complete checkout
            const checkoutResponse = await apiClient.checkout({
                firstName: userData.firstName,
                lastName: userData.lastName,
                postalCode: userData.postalCode
            });

            expect(checkoutResponse.success).to.be.true;
            expect(checkoutResponse.orderNumber).to.exist;
        } catch (error) {
            // Skip test if API is not available
            console.log('API not available, skipping test');
            this.skip();
        }
    });
});

10.7 CI/CD Integration

Set up CI/CD integration for the test framework:

GitHub Actions Workflow

# .github/workflows/wdio-tests.yml
name: WebDriverIO Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '0 0 * * *'  # Run daily at midnight

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x]

    steps:
    - uses: actions/checkout@v2

    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}

    - name: Install dependencies
      run: npm ci

    - name: Install Chrome
      run: |
        wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
        echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
        sudo apt-get update
        sudo apt-get install -y google-chrome-stable

    - name: Run WebDriverIO tests
      run: npm test

    - name: Upload test results
      uses: actions/upload-artifact@v2
      if: always()
      with:
        name: test-results
        path: |
          reports/
          screenshots/

    - name: Generate Allure Report
      if: always()
      run: npx allure generate reports/allure-results --clean -o allure-report

    - name: Upload Allure Report
      uses: actions/upload-artifact@v2
      if: always()
      with:
        name: allure-report
        path: allure-report

Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent {
        docker {
            image 'node:14-buster'
            args '-p 5900:5900 -v /dev/shm:/dev/shm'
        }
    }

    stages {
        stage('Setup') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Lint') {
            steps {
                sh 'npm run lint'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
            post {
                always {
                    archiveArtifacts artifacts: 'screenshots/**/*', allowEmptyArchive: true
                    junit 'reports/junit/*.xml'
                }
            }
        }

        stage('Generate Report') {
            steps {
                sh 'npx allure generate reports/allure-results --clean -o allure-report'
            }
            post {
                always {
                    archiveArtifacts artifacts: 'allure-report/**/*', allowEmptyArchive: true
                }
            }
        }
    }

    post {
        always {
            cleanWs()
        }
    }
}

10.8 Documentation

Create comprehensive documentation for the test framework:

README.md

# E-Commerce Test Automation Framework

This repository contains an automated testing framework for the Sauce Demo e-commerce website using WebDriverIO.

## Features

- Page Object Model design pattern
- Custom commands and utilities
- Data-driven testing
- Visual regression testing
- API testing integration
- Comprehensive reporting
- CI/CD integration

## Prerequisites

- Node.js (v14 or higher)
- npm (v6 or higher)
- Chrome browser

## Installation

1. Clone the repository:
  

git clone https://github.com/yourusername/e-commerce-tests.git cd e-commerce-tests


2. Install dependencies:

npm install ```

Running Tests

Run all tests:

npm test

Run specific test suite:

npm run test:login
npm run test:products
npm run test:cart
npm run test:checkout

Run tests in specific environment:

npm run test:local
npm run test:ci

Run visual regression tests:

npm run test:visual

Project Architecture

Project Structure

Page Objects

The framework uses the Page Object Model pattern to create an abstraction layer for the web pages. Each page object represents a page or a component of the application and encapsulates the page structure and behavior.

Example:

class LoginPage extends BasePage {
    constructor() {
        super();
        this.url = '/';
        this.usernameInput = $('#user-name');
        this.passwordInput = $('#password');
        this.loginButton = $('#login-button');
    }

    async login(username, password) {
        await this.usernameInput.setValue(username);
        await this.passwordInput.setValue(password);
        await this.loginButton.click();
    }
}

Custom Commands

The framework extends WebDriverIO with custom commands to simplify test scripts:

Example:

browser.addCommand('login', async function(username, password) {
    await browser.url('/');
    await $('#user-name').setValue(username);
    await $('#password').setValue(password);
    await $('#login-button').click();
});

Reports

Test reports are generated in multiple formats:

To generate and open the Allure report:

npm run report

CI/CD Integration

The framework includes configuration files for:

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details. ```

10.9 Practical Exercises

Exercise 1: Add New Test Cases

Extend the test suite with additional test cases:

  1. Test product filtering functionality
  2. Test user profile management
  3. Test error scenarios during checkout
  4. Test performance metrics for critical pages

Exercise 2: Enhance Reporting

Improve the reporting capabilities:

  1. Add custom Allure annotations for better test categorization
  2. Implement a custom reporter that sends results to Slack
  3. Add video recording for test execution
  4. Create a dashboard for test results visualization

Exercise 3: Implement Cross-Browser Testing

Set up cross-browser testing:

  1. Configure WebDriverIO to run tests on multiple browsers (Chrome, Firefox, Safari)
  2. Implement browser-specific code where necessary
  3. Set up parallel execution across browsers
  4. Create a report that compares test results across browsers

Summary

This module provided a comprehensive real-world project that brings together all the concepts and techniques learned throughout the WebDriverIO course. By implementing a complete test automation framework for an e-commerce application, you've applied best practices for test organization, maintainability, and reliability.

Key takeaways:

  1. A well-structured test framework improves maintainability and scalability
  2. Page Object Model provides a clean abstraction of the application under test
  3. Custom commands and utilities enhance test readability and reduce code duplication
  4. Multiple testing approaches (UI, visual, API) provide comprehensive coverage
  5. CI/CD integration ensures continuous quality assurance
  6. Proper documentation makes the framework accessible to all team members

You now have the skills and knowledge to design, implement, and maintain professional-grade test automation frameworks using WebDriverIO.

Previous: TestNG Integration Next: Java Course Overview