Skip to content

Este repositorio contiene un boilerplate (plantilla inicial) para empezar a trabajar con WebdriverIO, un framework de automatización de pruebas para navegadores web y aplicaciones móviles.

Notifications You must be signed in to change notification settings

charlyautomatiza/boilerplate-webdriverio

Repository files navigation

Instagram Twitch

WebdriverIO

WebdriverIO Boilerplate

This repository contains a starter boilerplate for working with WebdriverIO, a test automation framework for web browsers and mobile applications.

Prerequisites

Make sure you have Node.js installed. Download it from nodejs.org.

Recommended setup resources:

Installation

  1. Clone this repository:
git clone https://github.com/charlyautomatiza/boilerplate-webdriverio.git
  1. Change into the project directory:
cd boilerplate-webdriverio
  1. Install dependencies:
npm install

Running Tests

Download the latest release of the WebdriverIO Native Demo (Guinea Pig) App for Android (and iOS if needed).

Create a folder named app at the project root and place the APK there to avoid path issues.

After configuring your environment variables (ANDROID_HOME / Java etc.), run the tests:

npm run wdio

This command runs the E2E tests and produces Allure + JUnit reports.

Open the Combined Allure Report

npm run open-report

Continuous Integration: Android Emulator (GitHub Actions)

The included GitHub Actions workflow automatically runs the E2E tests on an Android emulator. It:

  • Downloads the demo APK into app/
  • Grants required emulator permissions
  • Boots an Android emulator (API 34 / Android 14) via reactivecircus/android-emulator-runner
  • Installs dependencies (Node.js, Appium, WebdriverIO, etc.)
  • Executes the E2E tests
  • Publishes Allure and JUnit results as artifacts

Triggers: on every push or pull request to main.

See .github/workflows/android-emulator.yml for full details.

Advanced Examples in test.e2e.ts

This project showcases advanced testing patterns (order reflects appearance in the spec file):

1. Basic Login Test

Validates the core happy path (sanity check for environment, selectors, and app readiness).

it('should login with valid credentials', async () => {
    await LoginPage.loginBtn.click();
    await LoginPage.login('[email protected]', 'SuperSecretPassword!');
    await expect(AlertPage.messageAlert).toHaveText(expect.stringContaining('You are logged in!'));
});

2. AAA Pattern (Arrange-Act-Assert)

Clarifies test intent by separating setup, action, and verification.

it('should login successfully following the AAA pattern', async () => {
    // Arrange
    await LoginPage.loginBtn.click();
    await expect(LoginPage.inputUsername).toBeDisplayed();
    await expect(LoginPage.inputPassword).toBeDisplayed();
    await expect(LoginPage.btnSubmit).toBeDisplayed();
    // Act
    await LoginPage.login('[email protected]', 'SuperSecretPassword!');
    // Assert
    await expect(AlertPage.messageAlert).toHaveText(
        expect.stringContaining('You are logged in!'),
        { message: 'Confirmation message is not the expected one' }
    );
});

3. Data-Driven Testing (DDT) with JSON

Each JSON row generates its own test case using a for...of loop for granular reporting.

// Load JSON data once
const jsonData: LoginData[] = JSON.parse(fs.readFileSync(filePath, 'utf-8'));

for (const row of jsonData) {
    it(`DDT JSON - User: "${row.username}" → message: "${row.expectedMessage}"`, async () => {
        // Arrange
        await LoginPage.loginBtn.click();
        // Act
        await LoginPage.login(row.username, row.password);
        // Assert (implicit wait via expect)
        await expect(AlertPage.messageAlert).toHaveText(
            expect.stringContaining(row.expectedMessage),
            { message: `Expected message: "${row.expectedMessage}" for user: ${row.username}` }
        );
    });
}

Data file: test/data/loginData.json

[
  { "username": "[email protected]", "password": "SuperSecretPassword!", "expectedMessage": "You are logged in!" },
  { "username": "[email protected]", "password": "TestPass123!", "expectedMessage": "You are logged in!" },
  { "username": "[email protected]", "password": "Admin2024!", "expectedMessage": "You are logged in!" }
]

4. Data-Driven Testing (DDT) with CSV

CSV parsing transforms raw text into structured objects. Each row becomes an independent test for clearer failure isolation.

// Load and parse CSV at runtime
const csvContent = fs.readFileSync(filePath, 'utf-8');
const lines = csvContent.trim().split('\n');

const userData: UserData[] = lines.slice(1).map(line => {
    const values = line.split(',').map(v => v.replace(/"/g, '').trim());
    return {
        username: values[0],
        password: values[1],
        expectedAction: values[2]
    };
});

for (const row of userData) {
    it(`DDT CSV - User: "${row.username}" → expected action: ${row.expectedAction}` , async () => {
        // Arrange
        await LoginPage.loginBtn.click();
        // Act
        await LoginPage.login(row.username, row.password);
        // Assert
        await expect(AlertPage.messageAlert).toHaveText(
            expect.stringContaining(row.expectedAction)
        );
    });
}

Data file: test/data/buttonsData.csv

username,password,expectedAction
[email protected],Pass123!,login_success
[email protected],Pass456!,login_success

(Moved Above) AAA Pattern (Arrange-Act-Assert)

Already documented as example 2 to reflect execution order.

it('should login successfully following the AAA pattern', async () => {
    // Arrange
    await LoginPage.loginBtn.click();
    await expect(LoginPage.inputUsername).toBeDisplayed();
    await expect(LoginPage.inputPassword).toBeDisplayed();
    await expect(LoginPage.btnSubmit).toBeDisplayed();
    // Act
    await LoginPage.login('[email protected]', 'SuperSecretPassword!');
    // Assert
    await expect(AlertPage.messageAlert).toHaveText(
        expect.stringContaining('You are logged in!'),
        { message: 'Confirmation message is not the expected one' }
    );
});

4. Good Practices Implemented

a. Granular DDT via for...of

Avoid forEach for async tests—for...of preserves proper async flow and allows dynamic test titles.

b. Implicit waits through expect

expect(...).toHaveText() inherently waits for the condition, reducing flakiness versus fixed sleeps.

c. Clear assertion messages

Custom messages help pinpoint data row failures quickly.

d. Page Object Model (POM)

All app interactions go through LoginPage and AlertPage, centralizing selectors and actions.

// test/pageobjects/login.page.ts
class LoginPage {
    public get loginBtn () {
        return $('~Login');
    }

    public get inputUsername () {
        return $('~input-email');
    }

    public get inputPassword () {
        return $('~input-password');
    }

    public async login (username: string, password: string) {
        await this.inputUsername.setValue(username);
        await this.inputPassword.setValue(password);
        await this.btnSubmit.click();
    }
}

File Structure

test/
├── data/
│   ├── loginData.json       # Data file for JSON-driven tests
│   └── buttonsData.csv      # Data file for CSV-driven tests
├── pageobjects/
│   ├── login.page.ts        # Login Page Object
│   └── alert.page.ts        # Alert Page Object
├── types/
│   └── data.ts              # Shared TypeScript interfaces (LoginData, UserData)
└── specs/
    └── test.e2e.ts          # E2E tests (basic, AAA, JSON DDT, CSV DDT)

Execution & Reporting Summary

Current suite produces (in execution order):

  1. 1 basic login test
  2. 1 AAA pattern test
  3. 3 JSON DDT test cases
  4. 2 CSV DDT test cases

Allure & JUnit reports show distinct titles per dataset row for clarity.

Rationale per Example

  1. Basic Login: Verifies essential flow and environment stability before deeper scenarios.
  2. AAA Pattern: Reinforces explicit structure for clarity and future maintenance.
  3. JSON DDT: Scales rapidly—add rows to expand coverage without modifying test logic.
  4. CSV DDT: Demonstrates ingestion of spreadsheet-style data for flexible scenario expansion.

Recommended Improvements (Next Steps)

  1. Extract interfaces (LoginData, UserData) outside the describe for reuse.
  2. Add negative login scenarios (invalid password / empty fields) to demonstrate error handling.
  3. Add screenshot / log attachment steps on failure integrated into Allure.
  4. Parameterize device capabilities for multi-platform matrix (Android/iOS).
  5. Introduce test tagging to selectively run subsets (e.g., smoke vs regression).

Mobile Test Automation Anti-Patterns to Avoid

Anti-Pattern Why It Hurts Better Approach
Hard-coded sleeps (e.g. browser.pause(3000)) Brittle & slow; ignores real UI readiness Implicit waits via WebdriverIO expectations (toBeDisplayed, toHaveText)
Using forEach for async test generation Does not await properly; can create false positives Use for...of to generate discrete it blocks
Mixing test logic & selectors inline Duplicates selectors; hard to refactor Page Object Model centralizes selectors/actions
Long monolithic test cases Hard to debug; single failure hides others Split by scenario/data; keep tests focused
Global mutable state between tests Causes leakage & flaky order-dependent results Reset app/session in hooks (afterTest)
Overuse of generic XPath selectors Fragile when UI shifts; performance overhead Prefer accessibility IDs / resource IDs
Ignoring platform differences Unexpected failures on iOS vs Android Abstract platform-specific selectors/methods
Silent assertions (no message) Harder triage in large suites Provide assertion messages with contextual data
No reporting attachments Limited diagnostics for CI failures Capture screenshots/logcat on failure
Testing multiple flows in one test One failure invalidates many checks Keep single, clear purpose per test

Rationale for Restart Strategy

Ensuring a clean app state after each test prevents leakage (e.g., leftover logged-in session) that can hide defects. The current afterTest hook invokes browser.relaunchActiveApp(), offering a lightweight refresh versus a full session recreation, reducing execution time while still clearing in-app UI state.

Design Principles Emphasized

  • Deterministic tests (no arbitrary waits)
  • Readable structure (AAA, clear titles)
  • Data externalization for scalability
  • Fast failure diagnosis (granular test cases + messages)
  • Maintainability (POM encapsulation)

Why Shared Types Matter

Using centralized TypeScript interfaces (LoginData, UserData in test/types/data.ts) provides several concrete benefits:

  1. Single Source of Truth: Changing a field (e.g. renaming expectedMessage) updates all specs and utilities at compile time—no silent drift.
  2. Early Feedback: Type errors surface during development instead of causing runtime undefined or assertion mismatches in CI.
  3. Safer Refactors: IDE auto-complete and rename tools work reliably across the suite, reducing regressions in large data-driven expansions.
  4. Data Contract Clarity: New contributors immediately see required fields and their intent, lowering onboarding friction.
  5. Prevents “Magic” Fields: Explicit interfaces discourage ad‑hoc additions to JSON/CSV that tests forget to assert.
  6. Enables Reuse: Utilities (parsers, generators, factories) can accept typed objects, improving composability and test scaffolding.
  7. Facilitates Lint & Static Analysis: Linters and quality tools (e.g. SonarQube) reason better about well-defined shapes versus dynamic objects.

Anti‑Pattern Avoided: Defining interfaces inline inside each describe causes duplication and accidental divergence; centralizing them avoids this drift.

Contributing

Feel free to open issues or submit pull requests for enhancements and fixes.

Support

Need help? Open an issue. More info: WebdriverIO Docs.

Additional content & updates: Website • Community Discord: Join here.

About

Este repositorio contiene un boilerplate (plantilla inicial) para empezar a trabajar con WebdriverIO, un framework de automatización de pruebas para navegadores web y aplicaciones móviles.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •