level 01 / generics-and-utility-types
Generics & Utility Types
Generic functions and TypeScript's built-in utility types — Partial, Required, Pick, Record, Readonly — used throughout Playwright fixtures and test data builders.
Generics
Generics let you write functions and types that work with many types while preserving type information:
// Without generics — loses type information
function first(arr: any[]): any {
return arr[0];
}
// With generics — preserves type
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const names = ['Alice', 'Bob', 'Carol'];
const name = first(names); // inferred: string | undefined
const counts = [3, 1, 4, 1, 5];
const count = first(counts); // inferred: number | undefined
Generic constraints
// T must have a name property
function printName<T extends { name: string }>(item: T): void {
console.log(item.name);
}
// T must be a key of the object
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, username: 'alice', email: 'alice@test.com' };
const username = getProperty(user, 'username'); // string
const id = getProperty(user, 'id'); // number
// getProperty(user, 'missing'); // Error at compile time
Utility types
TypeScript ships with utility types that transform existing types. These appear throughout Playwright fixture and configuration types.
Partial — make all properties optional
interface LoginCredentials {
username: string;
password: string;
twoFactorCode: string;
}
// For test data builders — not all fields required at construction time
type PartialCredentials = Partial<LoginCredentials>;
// { username?: string; password?: string; twoFactorCode?: string }
function buildCredentials(overrides: Partial<LoginCredentials> = {}): LoginCredentials {
return {
username: 'standard_user',
password: 'secret_sauce',
twoFactorCode: '000000',
...overrides,
};
}
Required — make all properties required
interface TestConfig {
baseURL?: string;
timeout?: number;
}
type RequiredConfig = Required<TestConfig>;
// { baseURL: string; timeout: number }
Pick — select specific properties
interface User {
id: number;
username: string;
email: string;
role: string;
createdAt: Date;
}
// Only need these fields for login tests
type LoginUser = Pick<User, 'username' | 'email'>;
// { username: string; email: string }
Record — typed key-value map
// Map browser names to their expected headings
type BrowserName = 'chromium' | 'firefox' | 'webkit';
const browserVersions: Record<BrowserName, string> = {
chromium: '120.0',
firefox: '121.0',
webkit: '17.4',
};
// Test data indexed by test ID
const testData: Record<string, { username: string; expected: string }> = {
'test-001': { username: 'standard_user', expected: 'Products' },
'test-002': { username: 'locked_out_user', expected: 'Epic sadface' },
};
Readonly — prevent mutation
// Playwright's Page is effectively readonly — you don't replace it
const config: Readonly<{ baseURL: string; timeout: number }> = {
baseURL: 'https://saucedemo.com',
timeout: 30_000,
};
// config.timeout = 5_000; // Error: Cannot assign to readonly property