level 01 / functions-and-async
Functions & Async/Await
Function types, arrow functions, and the async/await patterns that every Playwright test uses — with real examples from test fixtures and page objects.
Function signatures
TypeScript functions declare the types of their parameters and return value:
// Named function with typed params and return
function validateAge(age: number): boolean {
return age >= 18 && age <= 65;
}
// Arrow function — same semantics, shorter syntax
const validateAge = (age: number): boolean => age >= 18 && age <= 65;
// Optional parameter (must come after required params)
function login(username: string, password?: string): void {
// password may be undefined
}
// Default parameter
function navigate(url: string, timeout: number = 30_000): void {
// timeout defaults to 30_000 if not provided
}
// Rest parameter — collects remaining args into an array
function logSteps(...steps: string[]): void {
steps.forEach((s, i) => console.log(`${i + 1}. ${s}`));
}
Arrow functions
// Arrow function — concise for callbacks
const double = (n: number): number => n * 2;
// No return type needed when inferred
const greet = (name: string) => `Hello, ${name}`;
// No parens needed for single parameter (style preference — parens are safer)
const square = (n: number) => n * n;
// Multi-line arrow function needs curly braces and explicit return
const processResult = (status: string): string => {
if (status === 'pass') return '✓';
if (status === 'fail') return '✕';
return '○';
};
Promises and async/await
Playwright is entirely async. Every page interaction returns a Promise.
// Promise<T> — represents a value that will be available later
function fetchUser(id: number): Promise<{ name: string; email: string }> {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// async/await — syntactic sugar over Promises
async function fetchUser(id: number): Promise<{ name: string; email: string }> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// In Playwright — every test callback is async
test('user can log in', async ({ page }) => {
await page.goto('https://www.saucedemo.com');
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Products')).toBeVisible();
});
Key insight: Every
await in a Playwright test is a checkpoint. If you forget await, the assertion runs before the action completes — a common source of flakiness.Error handling in async code
// try/catch wraps awaited operations
async function safeNavigate(page: Page, url: string): Promise<boolean> {
try {
await page.goto(url, { timeout: 5_000 });
return true;
} catch (error) {
console.error(`Navigation failed: ${url}`, error);
return false;
}
}
// Parallel async operations
async function runParallel(page: Page): Promise<void> {
// await both concurrently — faster than sequential awaits
const [title, isVisible] = await Promise.all([
page.title(),
page.getByRole('button', { name: 'Login' }).isVisible(),
]);
console.log(title, isVisible);
}
Void vs never vs undefined
// void — function returns nothing meaningful (side effects only)
function logResult(msg: string): void {
console.log(msg);
}
// never — function never returns (throws or loops forever)
function fail(message: string): never {
throw new Error(message);
}
// undefined — an explicit missing value
function findUser(id: number): string | undefined {
if (id < 0) return undefined;
return `user-${id}`;
}