πŸ’‘ If you like this website, please share it with your friends and network! πŸš€

Advanced Framework Typing

Welcome to the advanced class! When building a massive enterprise Playwright framework, you want to write code *once* and reuse it everywhere. These advanced TypeScript features allow us to build highly flexible, reusable helper functions that automatically adapt to whatever data you throw at them. Let's dive in!

1. Basic Generics

Think of Generics () as a "placeholder variable for a Type". Instead of writing a helper function that only works for Users, you use to create a helper that works for *anything*.

typescript

// The  acts as a placeholder. Whatever type we pass in, it returns a Promise of that type.

async function fetchApiData(endpoint: string): Promise {

const response = await page.request.get(endpoint);

return response.json() as Promise;

}

// We pass the type, so TS knows 'userData' has an id and name!

const userData = await fetchApiData('/api/users/1');

2. Utility Types

Sometimes you have a massive interface (like a User with 50 fields), but for a specific test, you only want to update *one* field. Utility types like Partial let you make all fields optional instantly!

typescript

interface UserProfile { id: string; name: string; email: string; role: string; }

// Partial makes every field optional, so we don't have to provide all 4 fields just to update the email!

type UpdateUserPayload = Partial;

const updateData: UpdateUserPayload = { email: 'new@careerraah.com' }; // Perfectly valid!

3. Keyof

The keyof operator grabs all the keys of an object and turns them into a strict union type. This prevents typos when a function expects a property name.

typescript

interface Car { brand: string; year: number; }

// You can ONLY pass 'brand' or 'year' into this function.

function getCarProperty(property: keyof Car) {

console.log(Fetching ${property}...);

}

getCarProperty('brand'); // Works!

// getCarProperty('color'); // TS Error: Argument of type '"color"' is not assignable.

4. Type Guards

When an API returns mixed data (e.g., an array containing both strings and numbers), Type Guards help us safely separate them before acting on them.

typescript

const responseData: (string | number)[] = ['success', 404, 'pending'];

for (const item of responseData) {

if (typeof item === 'number') {

// TS now guarantees 'item' is a number here, so we can do math!

console.log(item + 10);

} else {

// TS knows it must be a string here.

console.log(item.toUpperCase());

}

}

5. Conditional Types

This is like writing an if/else statement, but for *Types* instead of values! It allows our framework to dynamically choose a return type based on what we input.

typescript

// If T is a string, return a boolean. Otherwise, return a number.

type IsString = T extends string ? boolean : number;

let result1: IsString<"Hello">; // Type becomes 'boolean'

let result2: IsString<42>; // Type becomes 'number'

6. Mapped Types

Mapped types act like a for...loop to iterate over an existing type and modify all its properties at once.

typescript

interface Settings { theme: string; notifications: boolean; }

// Loops over the keys of Settings and changes their values to boolean

type ToggleSettings = {

[Key in keyof Settings]: boolean;

};

// Now 'theme' and 'notifications' both MUST be booleans!

const userToggles: ToggleSettings = { theme: true, notifications: false };

7. Index Signatures

Sometimes an API returns a massive JSON object where you *don't know* what the keys will be in advance. Index signatures let you say, "This object will have string keys, and any kind of value."

typescript

// We don't know the keys, but we know they are strings, and the values are strings.

interface DynamicHeaders {

[key: string]: string;

}

const headers: DynamicHeaders = {

"Authorization": "Bearer token",

"X-Custom-Header": "careerraah-123"

};

8. Declaration Merging

If you declare an interface twice, TypeScript automatically merges them together! We use this in Playwright to easily add our own custom fixtures to Playwright's default TestFixtures object.

typescript

// First declaration

interface Box { height: number; }

// Second declaration somewhere else

interface Box { width: number; }

// TypeScript merged them!

const myBox: Box = { height: 10, width: 20 };

9. Decorators

Decorators (@) are a meta-programming feature that let you wrap a function to modify its behavior without changing its code. They are incredibly useful for adding automatic logging or retries to Page Object methods!

typescript

// A decorator that automatically logs whenever a method is called

function LogClick(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

const originalMethod = descriptor.value;

descriptor.value = function(...args: any[]) {

console.log([UI Action]: Clicking element...);

return originalMethod.apply(this, args);

};

}

class LoginPage {

@LogClick

async submitBtn() {

await page.locator('#login').click();

}

}

    CareerRaah: The Ultimate Interview Preparation Platform