
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.
•