Q2 of 38 · TypeScript
Explain TypeScript generics with a Page Object example.
Short answer
Short answer: Generics let you write a function or class that works with any type while keeping full type safety — the caller provides the concrete type at usage time. In test automation, generics appear in reusable Page Object base classes, typed API response wrappers, fixture factories, and assertion helpers.
Detail
Without generics, you'd write either any (losing type safety) or a separate implementation for every type (violating DRY). Generics give you a placeholder type parameter — conventionally T, K, or something descriptive — that gets resolved to a real type at the call site.
The classic test automation use case is a typed base Page Object:
class BasePage<TLocators extends Record<string, string>> {
constructor(
protected readonly page: Page,
protected readonly locators: TLocators
) {}
async fill(key: keyof TLocators, value: string) {
await this.page.fill(this.locators[key], value);
}
}
When LoginPage extends BasePage<typeof loginLocators>, calling fill("emailInput", "...") is type-checked — TypeScript knows "emailInput" is a valid key and will error at compile time if you typo the locator name.
Constraints (extends) narrow what types are acceptable. T extends Record<string, unknown> means "any T that is an object" — you get full autocomplete on properties while still being generic.
Utility types are essentially built-in generic type aliases: Partial<T>, Required<T>, Pick<T, K>, ReturnType<F>. Knowing at least five by name is table stakes for maintainable TypeScript test code.
// EXAMPLE
generic-api-wrapper.ts
import type { Page } from "@playwright/test";
// Generic API response wrapper for typed assertions
type ApiResponse<T> = {
status: number;
body: T;
headers: Record<string, string>;
};
async function getTypedResponse<T>(
page: Page,
url: string
): Promise<ApiResponse<T>> {
const response = await page.request.get(url);
const body = (await response.json()) as T;
return {
status: response.status(),
body,
headers: response.headers(),
};
}
// T is provided at the call site — autocomplete and type errors work
type UserProfile = { id: number; name: string; email: string };
const { body, status } = await getTypedResponse<UserProfile>(
page,
"/api/users/1"
);
console.log(body.name); // typed as string
console.log(body.id); // typed as number
// body.nonExistent; // TypeScript error at compile time