Back to Blog
Technology
August 26, 2025
4 min read
636 words

Building a Modern Selenium Framework with TypeScript

Step-by-step guide to creating a type-safe, maintainable Selenium test framework using TypeScript, WebDriverIO, and modern testing practices.

Building a Modern Selenium Framework with TypeScript

Why TypeScript for Selenium?

TypeScript has transformed how we write test automation code. Type safety catches errors at compile time, IDE support improves productivity, and the code becomes self-documenting. Here is how to build a production-ready Selenium framework with TypeScript.

Project Setup

Initialize Project

# Create project
mkdir selenium-typescript-framework
cd selenium-typescript-framework
npm init -y

# Install dependencies
npm install typescript selenium-webdriver @types/selenium-webdriver
npm install --save-dev ts-node @types/node mocha @types/mocha chai @types/chai
npm install --save-dev allure-commandline @wdio/allure-reporter

TypeScript Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Type-Safe Driver Factory

// src/core/driver-factory.ts
import { Builder, WebDriver, Capabilities } from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome';
import firefox from 'selenium-webdriver/firefox';

export type BrowserType = 'chrome' | 'firefox' | 'edge';

interface DriverOptions {
  browser: BrowserType;
  headless?: boolean;
  windowSize?: { width: number; height: number };
}

export class DriverFactory {
  static async createDriver(options: DriverOptions): Promise {
    const { browser, headless = false, windowSize } = options;
    let driver: WebDriver;

    switch (browser) {
      case 'chrome':
        const chromeOptions = new chrome.Options();
        if (headless) chromeOptions.addArguments('--headless=new');
        if (windowSize) {
          chromeOptions.addArguments(
            `--window-size=${windowSize.width},${windowSize.height}`
          );
        }
        driver = await new Builder()
          .forBrowser('chrome')
          .setChromeOptions(chromeOptions)
          .build();
        break;

      case 'firefox':
        const firefoxOptions = new firefox.Options();
        if (headless) firefoxOptions.addArguments('-headless');
        driver = await new Builder()
          .forBrowser('firefox')
          .setFirefoxOptions(firefoxOptions)
          .build();
        break;

      default:
        throw new Error(`Unsupported browser: ${browser}`);
    }

    return driver;
  }
}

Type-Safe Base Page

// src/pages/base.page.ts
import { WebDriver, WebElement, By, until } from 'selenium-webdriver';

export abstract class BasePage {
  protected driver: WebDriver;
  protected timeout: number = 30000;

  constructor(driver: WebDriver) {
    this.driver = driver;
  }

  protected async findElement(locator: By): Promise {
    await this.driver.wait(until.elementLocated(locator), this.timeout);
    return this.driver.findElement(locator);
  }

  protected async click(locator: By): Promise {
    const element = await this.findElement(locator);
    await this.driver.wait(until.elementIsVisible(element), this.timeout);
    await this.driver.wait(until.elementIsEnabled(element), this.timeout);
    await element.click();
  }

  protected async type(locator: By, text: string): Promise {
    const element = await this.findElement(locator);
    await element.clear();
    await element.sendKeys(text);
  }

  protected async getText(locator: By): Promise {
    const element = await this.findElement(locator);
    return element.getText();
  }

  protected async isDisplayed(locator: By): Promise {
    try {
      const element = await this.driver.findElement(locator);
      return await element.isDisplayed();
    } catch {
      return false;
    }
  }

  async takeScreenshot(name: string): Promise {
    const screenshot = await this.driver.takeScreenshot();
    // Save screenshot logic
    return screenshot;
  }
}

Implementing Page Objects

// src/pages/login.page.ts
import { By } from 'selenium-webdriver';
import { BasePage } from './base.page';

interface UserCredentials {
  username: string;
  password: string;
}

export class LoginPage extends BasePage {
  // Locators as typed properties
  private readonly usernameInput = By.id('username');
  private readonly passwordInput = By.id('password');
  private readonly submitButton = By.css('button[type="submit"]');
  private readonly errorMessage = By.className('error-message');

  async login(credentials: UserCredentials): Promise {
    await this.type(this.usernameInput, credentials.username);
    await this.type(this.passwordInput, credentials.password);
    await this.click(this.submitButton);
  }

  async getErrorMessage(): Promise {
    return this.getText(this.errorMessage);
  }

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

Type-Safe Test Fixtures

// src/fixtures/test-data.ts
export interface TestUser {
  username: string;
  password: string;
  email: string;
  email: 'test@example.com'
  },
  invalidUser: {
    username: 'invalid',
    password: 'wrong',
    email: 'invalid@example.com'
  }
};

Writing Tests with Types

// src/tests/login.test.ts
import { WebDriver } from 'selenium-webdriver';
import { expect } from 'chai';
import { DriverFactory } from '../core/driver-factory';
import { LoginPage } from '../pages/login.page';
import { testUsers } from '../fixtures/test-data';

describe('Login Tests', () => {
  let driver: WebDriver;
  let loginPage: LoginPage;

  before(async () => {
    driver = await DriverFactory.createDriver({
      browser: 'chrome',
      headless: true,
      windowSize: { width: 1920, height: 1080 }
    });
    loginPage = new LoginPage(driver);
  });

  after(async () => {
    await driver.quit();
  });

  it('should login successfully with valid credentials', async () => {
    await loginPage.login(testUsers.validUser);
    // Assert successful login
  });

  it('should display error for invalid credentials', async () => {
    await loginPage.login(testUsers.invalidUser);
    
    expect(await loginPage.isErrorDisplayed()).to.be.true;
  });
});

Key Takeaways

  • TypeScript provides compile-time error detection
  • Interfaces ensure consistent data structures
  • Type-safe factories prevent runtime browser errors
  • Abstract base classes enforce consistent patterns
  • Modern tooling like ts-node enables direct execution
Tags:TechnologyTutorialGuide
X

Written by XQA Team

Our team of experts delivers insights on technology, business, and design. We are dedicated to helping you build better products and scale your business.