TypeScript Arrays โ€” Complete Guide with map, filter, reduce & Playwright Examples

Arrays are everywhere in test automation โ€” storing test data, collecting page elements, managing results. TypeScript arrays come with strong typing that catches bugs before your tests even run. This guide covers everything from basics to advanced patterns with Playwright examples.

๐Ÿ“– What You’ll Learn: Array types, push/pop/splice, map/filter/reduce, spread operator, tuples, readonly arrays, and array operations in Playwright tests.


๐Ÿ“ฆ Declaring Arrays

// Two ways to declare typed arrays
let browsers: string[] = ['chromium', 'firefox', 'webkit'];
let scores: Array<number> = [85, 92, 78, 95];

// Mixed types with union
let mixed: (string | number)[] = ['hello', 42, 'world'];

// Empty array with type
let results: boolean[] = [];

// Array of objects
let users: { name: string; role: string }[] = [
  { name: 'Deepesh', role: 'SDET' },
  { name: 'John', role: 'QA Lead' },
];

// 2D array (matrix)
let grid: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

โž• Adding & Removing Elements

let fruits: string[] = ['apple', 'banana'];

// Add to end
fruits.push('cherry');           // ['apple', 'banana', 'cherry']

// Add to beginning
fruits.unshift('mango');         // ['mango', 'apple', 'banana', 'cherry']

// Remove from end
let last = fruits.pop();         // 'cherry', array: ['mango', 'apple', 'banana']

// Remove from beginning
let first = fruits.shift();      // 'mango', array: ['apple', 'banana']

// Remove/insert at position with splice
let colors: string[] = ['red', 'green', 'blue', 'yellow'];
colors.splice(1, 2);             // Remove 2 items from index 1 โ†’ ['red', 'yellow']
colors.splice(1, 0, 'purple');   // Insert at index 1 โ†’ ['red', 'purple', 'yellow']

// Find index and remove
let items = ['a', 'b', 'c', 'd'];
let idx = items.indexOf('c');
if (idx !== -1) items.splice(idx, 1);  // ['a', 'b', 'd']

๐Ÿ” Searching & Checking Arrays

let numbers: number[] = [10, 20, 30, 40, 50];

// includes โ€” does it contain a value?
console.log(numbers.includes(30));  // true

// indexOf โ€” find position (-1 if not found)
console.log(numbers.indexOf(40));   // 3

// find โ€” first element matching condition
let found = numbers.find(n => n > 25);
console.log(found);  // 30

// findIndex โ€” index of first match
let foundIdx = numbers.findIndex(n => n > 25);
console.log(foundIdx);  // 2

// every โ€” do ALL elements match?
let allPositive = numbers.every(n => n > 0);    // true

// some โ€” does ANY element match?
let hasLarge = numbers.some(n => n > 100);       // false

๐Ÿ”„ map, filter & reduce

let prices: number[] = [100, 200, 300, 400, 500];

// map โ€” transform each element
let discounted = prices.map(p => p * 0.9);
console.log(discounted);  // [90, 180, 270, 360, 450]

// filter โ€” keep elements matching condition
let expensive = prices.filter(p => p >= 300);
console.log(expensive);  // [300, 400, 500]

// reduce โ€” combine all elements into one value
let total = prices.reduce((sum, p) => sum + p, 0);
console.log(total);  // 1500

// Chaining โ€” filter + map + reduce
let premiumTotal = prices
  .filter(p => p >= 300)           // [300, 400, 500]
  .map(p => p * 1.1)               // [330, 440, 550]
  .reduce((sum, p) => sum + p, 0); // 1320
console.log(premiumTotal);

// flat โ€” flatten nested arrays
let nested: number[][] = [[1, 2], [3, 4], [5]];
let flat = nested.flat();
console.log(flat);  // [1, 2, 3, 4, 5]

๐Ÿ’ก map transforms, filter selects, reduce combines. Chain them together for powerful data pipelines. These are the three most important array methods to master.


๐Ÿ“Œ Spread, Destructuring & Tuples

// Spread operator โ€” copy and merge arrays
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2];    // [1, 2, 3, 4, 5, 6]
let copy = [...arr1];                 // [1, 2, 3] โ€” independent copy

// Destructuring โ€” extract values
let [first, second, ...rest] = [10, 20, 30, 40, 50];
console.log(first);  // 10
console.log(rest);   // [30, 40, 50]

// Tuple โ€” fixed-length, typed array
let coord: [number, number] = [40.7128, -74.0060];
let entry: [string, number, boolean] = ['test-1', 95, true];

// Readonly array โ€” cannot be modified
const readonlyBrowsers: readonly string[] = ['chromium', 'firefox', 'webkit'];
// readonlyBrowsers.push('edge');  // โŒ Error! Cannot modify

// Sorting
let nums = [30, 10, 50, 20, 40];
nums.sort((a, b) => a - b);          // [10, 20, 30, 40, 50] ascending
nums.sort((a, b) => b - a);          // [50, 40, 30, 20, 10] descending

๐ŸŽญ Arrays in Playwright โ€” Real Examples

import { test, expect } from '@playwright/test';

test('collect and verify all product names', async ({ page }) => {
  await page.goto('https://sqatools.in');
  
  // Collect all text content into an array
  const links = page.locator('nav a');
  const texts: string[] = await links.allTextContents();
  
  console.log('Navigation links:', texts);
  expect(texts.length).toBeGreaterThan(0);
  
  // Filter only links containing 'Playwright'
  const playwrightLinks = texts.filter(t => t.toLowerCase().includes('playwright'));
  console.log('Playwright links:', playwrightLinks);
});

// Data-driven testing with arrays
const testData: [string, string, boolean][] = [
  ['admin@test.com', 'admin123', true],
  ['invalid@test.com', 'wrong', false],
  ['', '', false],
];

for (const [email, password, shouldPass] of testData) {
  test(`login with ${email || 'empty'} should ${shouldPass ? 'pass' : 'fail'}`, async ({ page }) => {
    await page.goto('/login');
    await page.fill('#email', email);
    await page.fill('#password', password);
    await page.click('#submit');
    
    if (shouldPass) {
      await expect(page.locator('.dashboard')).toBeVisible();
    } else {
      await expect(page.locator('.error')).toBeVisible();
    }
  });
}

โ“ FAQ

๐Ÿค” What is the difference between string[] and Array<string>?

They are identical โ€” both declare an array of strings. string[] is shorter and more commonly used. Array<string> is clearer for complex types like Array<Map<string, number>>.


๐Ÿค” How do I get all text from Playwright locators into an array?

Use await locator.allTextContents() โ€” it returns a string[] with the text content of every matching element. For inner text, use allInnerTexts().

๐ŸŽฏ Next Steps

๐ŸŽฌ Watch the full TypeScript tutorial series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

TypeScript Functions โ€” Declarations, Arrow Functions, Async & Generics with Playwright

Functions are the building blocks of clean, reusable code. In test automation, you’ll use them everywhere โ€” from page actions to custom assertions to utility helpers. This guide covers every function pattern in TypeScript with real Playwright examples.

๐Ÿ“– What You’ll Learn: Function declarations, arrow functions, optional & default parameters, rest parameters, return types, overloads, generics in functions, and async functions in Playwright.


๐Ÿ”ง Function Declarations & Expressions

// Function declaration
function greet(name: string): string {
  return `Hello, ${name}!`;
}

// Function expression
const add = function(a: number, b: number): number {
  return a + b;
};

// Arrow function (most common in modern TypeScript)
const multiply = (a: number, b: number): number => a * b;

// Void function โ€” returns nothing
function logMessage(msg: string): void {
  console.log(`[LOG]: ${msg}`);
}

console.log(greet('QA Engineer'));  // Hello, QA Engineer!
console.log(add(5, 3));            // 8
console.log(multiply(4, 7));       // 28

โš™๏ธ Optional, Default & Rest Parameters

// Optional parameter (use ?)
function createUser(name: string, email?: string): string {
  return email ? `${name} (${email})` : name;
}
console.log(createUser('Deepesh'));                    // Deepesh
console.log(createUser('Deepesh', 'test@test.com'));   // Deepesh (test@test.com)

// Default parameter
function wait(ms: number = 1000): void {
  console.log(`Waiting ${ms}ms...`);
}
wait();      // Waiting 1000ms...
wait(3000);  // Waiting 3000ms...

// Rest parameters (variable number of args)
function logAll(...messages: string[]): void {
  messages.forEach((msg, i) => console.log(`${i + 1}. ${msg}`));
}
logAll('Step 1', 'Step 2', 'Step 3');

// Destructured parameters
function printConfig({ url, timeout = 30000 }: { url: string; timeout?: number }): void {
  console.log(`URL: ${url}, Timeout: ${timeout}`);
}
printConfig({ url: 'https://sqatools.in' });

๐Ÿ“ Function Types & Callbacks

// Type alias for a function
type Validator = (value: string) => boolean;

const isEmail: Validator = (value) => value.includes('@');
const isNotEmpty: Validator = (value) => value.trim().length > 0;

console.log(isEmail('test@test.com'));  // true
console.log(isNotEmpty(''));            // false

// Callback functions
function runValidation(input: string, validator: Validator, onResult: (passed: boolean) => void): void {
  const result = validator(input);
  onResult(result);
}

runValidation('hello@test.com', isEmail, (passed) => {
  console.log(passed ? 'โœ… Valid' : 'โŒ Invalid');
});

// Returning functions (higher-order)
function createMultiplier(factor: number): (n: number) => number {
  return (n) => n * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

๐Ÿงฌ Generic Functions

// Generic function โ€” works with any type
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

console.log(firstElement([1, 2, 3]));          // 1 (type: number)
console.log(firstElement(['a', 'b', 'c']));    // 'a' (type: string)

// Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Deepesh', role: 'SDET' };
console.log(getProperty(user, 'name'));  // 'Deepesh'

// Generic async function
async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return response.json() as Promise<T>;
}

๐Ÿ’ก Generics let you write ONE function that works with strings, numbers, objects โ€” anything. TypeScript figures out the type automatically when you call it.


โšก Async Functions

// Async function returns a Promise
async function fetchTitle(url: string): Promise<string> {
  const response = await fetch(url);
  const html = await response.text();
  const match = html.match(/<title>(.*)<\/title>/);
  return match ? match[1] : 'No title found';
}

// Async arrow function
const delay = async (ms: number): Promise<void> => {
  return new Promise(resolve => setTimeout(resolve, ms));
};

// Try-catch in async functions
async function safeRequest(url: string): Promise<string | null> {
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.text();
  } catch (error) {
    console.error('Request failed:', error);
    return null;
  }
}

๐ŸŽญ Functions in Playwright โ€” Real Examples

import { test, expect, Page } from '@playwright/test';

// Reusable login helper function
async function login(page: Page, email: string, password: string): Promise<void> {
  await page.goto('/login');
  await page.fill('#email', email);
  await page.fill('#password', password);
  await page.click('#login-btn');
  await expect(page.locator('.dashboard')).toBeVisible();
}

// Retry function for flaky operations
async function retry<T>(fn: () => Promise<T>, maxAttempts: number = 3): Promise<T> {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      console.log(`Attempt ${attempt} failed, retrying...`);
      await new Promise(r => setTimeout(r, 1000));
    }
  }
  throw new Error('Should not reach here');
}

// Using helper functions in tests
test('login and verify dashboard', async ({ page }) => {
  await login(page, 'admin@sqatools.in', 'test123');
  await expect(page).toHaveTitle(/Dashboard/);
});

test('retry flaky element click', async ({ page }) => {
  await page.goto('https://sqatools.in');
  await retry(async () => {
    await page.click('.dynamic-button');
    await expect(page.locator('.result')).toBeVisible();
  });
});

โ“ FAQ

๐Ÿค” When should I use arrow functions vs regular functions?

Use arrow functions for callbacks, short expressions, and class methods. Use regular functions when you need hoisting or a named function in stack traces. In Playwright, arrow functions are standard for test callbacks and helpers.


๐Ÿค” What is the difference between optional and default parameters?

Optional parameters (name?: string) can be omitted and will be undefined. Default parameters (name: string = 'Guest') have a fallback value when omitted. Default parameters are preferred when you have a sensible default.

๐ŸŽฏ Next Steps

๐ŸŽฌ Watch the full TypeScript tutorial series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

TypeScript Loops โ€” for, while, for…of & forEach with Playwright Examples

Loops let you repeat actions โ€” whether it’s iterating through test data, checking multiple elements, or retrying flaky operations. This guide covers every loop type in TypeScript with real Playwright examples you’ll use daily in test automation.

๐Ÿ“– What You’ll Learn: for loop, while loop, do…while, for…of, for…in, forEach, break/continue, and looping through Playwright elements.


๐Ÿ” for Loop

The classic loop โ€” you control the start, end, and step.

// Basic for loop
for (let i: number = 0; i < 5; i++) {
  console.log('Iteration:', i);
}
// Output: 0, 1, 2, 3, 4

// Counting backwards
for (let i: number = 10; i > 0; i -= 2) {
  console.log(i);
}
// Output: 10, 8, 6, 4, 2

// Looping through an array by index
let browsers: string[] = ['chromium', 'firefox', 'webkit'];
for (let i = 0; i < browsers.length; i++) {
  console.log(`Browser ${i + 1}: ${browsers[i]}`);
}

๐Ÿ”„ while and do...while Loops

// while โ€” checks condition BEFORE each iteration
let attempts: number = 0;
while (attempts < 3) {
  console.log(`Attempt ${attempts + 1}`);
  attempts++;
}

// do...while โ€” runs at least ONCE, then checks condition
let input: string = '';
do {
  input = 'valid';  // simulating user input
  console.log('Processing input:', input);
} while (input === '');

// Retry pattern (common in test automation)
let retryCount: number = 0;
let success: boolean = false;
while (!success && retryCount < 3) {
  try {
    // attempt some operation
    success = true;
    console.log('Operation succeeded!');
  } catch {
    retryCount++;
    console.log(`Retry ${retryCount}/3`);
  }
}

โš ๏ธ Always include a way to exit while loops (increment a counter, set a flag). An infinite loop will crash your tests!


๐ŸŽฏ for...of and for...in Loops

// for...of โ€” iterate over VALUES (arrays, strings, maps)
let testCases: string[] = ['login', 'signup', 'checkout'];
for (const tc of testCases) {
  console.log('Running test:', tc);
}

// for...in โ€” iterate over KEYS/INDICES (objects)
let config = { baseURL: 'https://sqatools.in', timeout: 30000, retries: 2 };
for (const key in config) {
  console.log(`${key}: ${config[key as keyof typeof config]}`);
}

// Iterating over a Map
let testData = new Map();
testData.set('user1', 'admin@test.com');
testData.set('user2', 'user@test.com');

for (const [key, value] of testData) {
  console.log(`${key} -> ${value}`);
}

๐Ÿ’ก Quick Rule: Use for...of for arrays and iterable values. Use for...in for object keys. Never use for...in on arrays โ€” it iterates over indices as strings, which causes bugs.


๐Ÿ“‹ forEach and Array Methods

let scores: number[] = [85, 92, 78, 95, 88];

// forEach โ€” run a function for each element
scores.forEach((score, index) => {
  console.log(`Student ${index + 1}: ${score}`);
});

// map โ€” transform each element into a new array
let grades = scores.map(s => s >= 90 ? 'A' : s >= 80 ? 'B' : 'C');
console.log(grades);  // ['B', 'A', 'C', 'A', 'B']

// filter โ€” keep only elements that match
let topScores = scores.filter(s => s >= 90);
console.log(topScores);  // [92, 95]

// find โ€” get the first match
let firstTop = scores.find(s => s >= 90);
console.log(firstTop);  // 92

// every/some โ€” check all or any
let allPassed = scores.every(s => s >= 60);   // true
let anyPerfect = scores.some(s => s === 100); // false

โน๏ธ break and continue

// break โ€” exit the loop entirely
for (let i = 0; i < 10; i++) {
  if (i === 5) break;
  console.log(i);
}
// Output: 0, 1, 2, 3, 4

// continue โ€” skip current iteration, go to next
for (let i = 0; i < 10; i++) {
  if (i % 2 === 0) continue;  // skip even numbers
  console.log(i);
}
// Output: 1, 3, 5, 7, 9

๐ŸŽญ Loops in Playwright โ€” Real Examples

import { test, expect } from '@playwright/test';

test('iterate through page elements', async ({ page }) => {
  await page.goto('https://sqatools.in');
  
  // Get all navigation links and verify each one
  const navLinks = page.locator('nav a');
  const count = await navLinks.count();
  
  for (let i = 0; i < count; i++) {
    const text = await navLinks.nth(i).textContent();
    const href = await navLinks.nth(i).getAttribute('href');
    console.log(`Link ${i + 1}: ${text} -> ${href}`);
    expect(href).not.toBeNull();
  }
});

// Data-driven test with for...of
const testUsers = [
  { email: 'admin@test.com', role: 'admin' },
  { email: 'user@test.com', role: 'user' },
  { email: 'guest@test.com', role: 'guest' },
];

for (const user of testUsers) {
  test(`login as ${user.role}`, async ({ page }) => {
    await page.goto('/login');
    await page.fill('#email', user.email);
    await page.fill('#password', 'test123');
    await page.click('#login-btn');
    await expect(page.locator('.role-badge')).toHaveText(user.role);
  });
}

โ“ FAQ

๐Ÿค” What is the difference between for...of and for...in?

for...of iterates over values (arrays, strings). for...in iterates over keys/indices (objects). Use for...of for arrays and for...in for object properties.


๐Ÿค” How do I loop through Playwright locators?

Use locator.count() to get the number of matches, then use a for loop with locator.nth(i) to access each element individually.

๐ŸŽฏ Next Steps

๐ŸŽฌ Watch the full TypeScript tutorial series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

TypeScript Conditions โ€” if/else, Switch & Type Guards with Playwright Examples

Conditions let you control which code runs based on different scenarios โ€” essential for building smart Playwright tests that handle popups, dynamic content, and different environments. This guide covers if/else, switch, ternary operators, and type guards with real test automation examples.

๐Ÿ“– What You’ll Learn: if/else statements, else if chains, switch/case, ternary operator, type narrowing with conditions, and real Playwright conditional flows.


๐Ÿ”€ if / else Statement

The most basic conditional โ€” execute code only when a condition is true.

let temperature: number = 30;

if (temperature > 25) {
  console.log("It's hot outside! โ˜€๏ธ");
} else {
  console.log("It's cool outside! ๐ŸŒค๏ธ");
}
// Output: "It's hot outside! โ˜€๏ธ"

๐Ÿ”— else if Chain

Check multiple conditions in sequence โ€” the first match wins.

let score: number = 85;
let grade: string;

if (score >= 90) {
  grade = "A โ€” Excellent!";
} else if (score >= 80) {
  grade = "B โ€” Good job!";
} else if (score >= 70) {
  grade = "C โ€” Average";
} else if (score >= 60) {
  grade = "D โ€” Below average";
} else {
  grade = "F โ€” Needs improvement";
}

console.log(grade);  // "B โ€” Good job!"

๐Ÿ’ก Tip: Order your conditions from most specific to least specific. TypeScript evaluates them top-to-bottom and stops at the first match.


๐Ÿ”„ Switch Statement

When you have many possible values for a single variable, switch is cleaner than multiple if/else statements.

type Browser = 'chromium' | 'firefox' | 'webkit';
let browserName: Browser = 'chromium';

switch (browserName) {
  case 'chromium':
    console.log('๐ŸŒ Running on Chrome/Edge');
    break;
  case 'firefox':
    console.log('๐ŸฆŠ Running on Firefox');
    break;
  case 'webkit':
    console.log('๐Ÿงญ Running on Safari');
    break;
  default:
    console.log('โ“ Unknown browser');
}

// Switch with return (in a function)
function getTimeout(env: string): number {
  switch (env) {
    case 'production': return 30000;
    case 'staging':    return 15000;
    case 'local':      return 5000;
    default:           return 10000;
  }
}

โšก Ternary Operator

A one-liner alternative to if/else โ€” perfect for simple conditions.

// Syntax: condition ? valueIfTrue : valueIfFalse

let isProduction: boolean = false;
let baseURL = isProduction ? 'https://app.com' : 'https://staging.app.com';

let userAge: number = 20;
let category = userAge >= 18 ? 'Adult' : 'Minor';

// Nested ternary (use sparingly!)
let speed: number = 100;
let zone = speed > 120 ? 'Danger' : speed > 80 ? 'Fast' : 'Normal';

โš ๏ธ Avoid nested ternaries โ€” they’re hard to read. Use if/else instead when the logic is complex.


๐Ÿ” Type Narrowing with Conditions

TypeScript gets smarter inside conditional blocks โ€” it narrows the type automatically.

// typeof narrowing
function formatValue(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase();  // TypeScript knows it's a string here
  } else {
    return value.toFixed(2);     // TypeScript knows it's a number here
  }
}

// Truthiness narrowing
function greetUser(name: string | null | undefined): string {
  if (name) {
    return 'Hello, ' + name;   // name is definitely a string here
  }
  return 'Hello, Guest';
}

// in operator narrowing
interface Dog { bark(): void; breed: string; }
interface Cat { meow(): void; color: string; }

function handlePet(pet: Dog | Cat) {
  if ('bark' in pet) {
    pet.bark();    // TypeScript knows it's a Dog
  } else {
    pet.meow();    // TypeScript knows it's a Cat
  }
}

๐ŸŽญ Conditions in Playwright โ€” Real Examples

import { test, expect } from '@playwright/test';

test('handle dynamic page content', async ({ page }) => {
  await page.goto('https://sqatools.in');
  
  // Close popup if it appears
  const popup = page.locator('.popup-overlay');
  if (await popup.isVisible({ timeout: 3000 }).catch(() => false)) {
    await popup.locator('.close-btn').click();
    console.log('Popup closed');
  }
  
  // Environment-based navigation
  const env = process.env.TEST_ENV ?? 'staging';
  switch (env) {
    case 'production':
      await page.goto('https://sqatools.in');
      break;
    case 'staging':
      await page.goto('https://staging.sqatools.in');
      break;
    default:
      await page.goto('http://localhost:3000');
  }
  
  // Conditional assertions based on page state
  const loginBtn = page.locator('#login-btn');
  const dashboardLink = page.locator('#dashboard');
  
  if (await loginBtn.isVisible()) {
    console.log('User is not logged in');
    await loginBtn.click();
  } else if (await dashboardLink.isVisible()) {
    console.log('User is already logged in');
  }
});

โ“ FAQ

๐Ÿค” When should I use switch vs if/else?

Use switch when comparing a single variable against multiple specific values. Use if/else when conditions are complex expressions or involve multiple variables.


๐Ÿค” What is type narrowing in TypeScript?

Type narrowing is when TypeScript automatically narrows a union type to a more specific type inside a conditional block. For example, after if (typeof x === 'string'), TypeScript knows x is a string.

๐ŸŽฏ Next Steps

๐ŸŽฌ Watch the full TypeScript tutorial series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

TypeScript Operators โ€” Complete Guide with Playwright Examples

Operators are the building blocks of every TypeScript program. Whether you’re writing Playwright test automation or building applications, understanding operators is essential. This guide covers every operator type in TypeScript with practical examples you can use in your Playwright projects.

๐Ÿ“– What You’ll Learn: Arithmetic, Comparison, Logical, Assignment, Ternary, Nullish Coalescing, and Type operators โ€” all with real Playwright examples.


โž• Arithmetic Operators

Arithmetic operators perform mathematical calculations on numbers.

let a: number = 10;
let b: number = 3;

console.log(a + b);   // 13  โ€” Addition
console.log(a - b);   // 7   โ€” Subtraction
console.log(a * b);   // 30  โ€” Multiplication
console.log(a / b);   // 3.33 โ€” Division
console.log(a % b);   // 1   โ€” Modulus (remainder)
console.log(a ** b);  // 1000 โ€” Exponentiation

// Increment and Decrement
let count: number = 5;
count++;   // 6 (post-increment)
count--;   // 5 (post-decrement)
++count;   // 6 (pre-increment)

โš–๏ธ Comparison Operators

Comparison operators compare two values and return a boolean (true or false). These are essential in Playwright assertions.

let x: number = 5;
let y: string = "5";

// Loose equality (compares value only, allows type coercion)
console.log(x == y);    // true โ€” NOT recommended in TypeScript

// Strict equality (compares value AND type)
console.log(x === 5);   // true โ€” ALWAYS use this
console.log(x === y);   // Error! TypeScript catches this at compile time

// Not equal
console.log(x != y);    // false (loose)
console.log(x !== 10);  // true (strict)

// Greater / Less than
console.log(x > 3);     // true
console.log(x >= 5);    // true
console.log(x < 10);    // true
console.log(x <= 4);    // false

โš ๏ธ Golden Rule: Always use === (strict equality) and !== in TypeScript. The loose operators == and != can cause subtle bugs that are hard to debug in test automation.


๐Ÿง  Logical Operators

Logical operators combine multiple conditions. You'll use these constantly in test automation for complex validations.

let isLoggedIn: boolean = true;
let isAdmin: boolean = false;
let hasPermission: boolean = true;

// AND (&&) โ€” ALL conditions must be true
console.log(isLoggedIn && isAdmin);         // false
console.log(isLoggedIn && hasPermission);    // true

// OR (||) โ€” at least ONE must be true
console.log(isAdmin || hasPermission);       // true

// NOT (!) โ€” reverses the boolean
console.log(!isLoggedIn);                    // false
console.log(!isAdmin);                       // true

// Combining multiple operators
if (isLoggedIn && (isAdmin || hasPermission)) {
  console.log("Access granted");  // This runs
}

๐Ÿ“ Assignment Operators

let num: number = 10;

num += 5;   // num = num + 5  โ†’ 15
num -= 3;   // num = num - 3  โ†’ 12
num *= 2;   // num = num * 2  โ†’ 24
num /= 4;   // num = num / 4  โ†’ 6
num %= 4;   // num = num % 4  โ†’ 2
num **= 3;  // num = num ** 3 โ†’ 8

โ“ Ternary & Nullish Coalescing Operators

These are shorthand operators that make your code cleaner and more readable.

// Ternary Operator: condition ? valueIfTrue : valueIfFalse
let age: number = 20;
let status: string = age >= 18 ? "Adult" : "Minor";
console.log(status);  // "Adult"

// Nullish Coalescing (??) โ€” returns right side if left is null/undefined
let username: string | null = null;
let displayName = username ?? "Guest";
console.log(displayName);  // "Guest"

// Optional Chaining (?.) โ€” safely access nested properties
interface User {
  name: string;
  address?: { city?: string };
}
let user: User = { name: "Deepesh" };
console.log(user.address?.city);  // undefined (no error!)

๐Ÿ” Type Operators (TypeScript-Specific)

// typeof โ€” check runtime type
let value: unknown = "hello";
if (typeof value === "string") {
  console.log(value.toUpperCase());  // "HELLO"
}

// instanceof โ€” check if object is an instance of a class
class Animal { name: string = "dog"; }
class Dog extends Animal { breed: string = "lab"; }

let pet = new Dog();
console.log(pet instanceof Dog);     // true
console.log(pet instanceof Animal);  // true

// keyof โ€” get union of object keys (compile-time only)
interface Config {
  baseURL: string;
  timeout: number;
}
type ConfigKey = keyof Config;  // "baseURL" | "timeout"

// as โ€” type assertion
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;

๐ŸŽญ Operators in Playwright โ€” Real Examples

import { test, expect } from '@playwright/test';

test('using operators in Playwright tests', async ({ page }) => {
  await page.goto('https://sqatools.in');
  
  // Comparison: check element count
  const links = await page.locator('a').count();
  expect(links > 0).toBeTruthy();
  
  // Logical: multiple conditions
  const title = await page.title();
  const isCorrectPage = title.includes('SQA') && links > 10;
  expect(isCorrectPage).toBe(true);
  
  // Nullish coalescing: safe text extraction
  const headerText = await page.locator('h1').first().textContent() ?? '';
  expect(headerText.length > 0).toBeTruthy();
  
  // Ternary: conditional actions
  const env = process.env.ENV ?? 'staging';
  const timeout = env === 'production' ? 30000 : 10000;
  
  // typeof: type guards for safe handling
  const attribute = await page.locator('body').getAttribute('class');
  if (typeof attribute === 'string') {
    console.log('Body classes:', attribute);
  }
});

โ“ FAQ

๐Ÿค” What is the difference between == and === in TypeScript?

== compares values with type coercion (loose equality), while === compares both value and type (strict equality). Always use === in TypeScript.


๐Ÿค” What is the nullish coalescing operator (??) in TypeScript?

The ?? operator returns the right-hand value when the left-hand value is null or undefined. Unlike ||, it doesn't treat 0, "", or false as "empty".


๐Ÿค” How do I use typeof in Playwright tests?

Use typeof to check the runtime type of values returned from page methods. For example, typeof (await page.title()) === 'string' ensures you're working with a string before calling string methods.

๐ŸŽฏ Next Steps

Now that you understand TypeScript operators, continue your learning journey:

๐ŸŽฌ Watch the full TypeScript tutorial series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

How to Test Passkey Login with Playwright โ€” WebAuthn Automation Guide

Passkeys are replacing passwords across the web, and if your test automation framework can’t handle WebAuthn flows, you’re already falling behind. Playwright 1.61 introduced browserContext.credentials โ€” a virtual authenticator that lets you register, store, and use passkeys entirely in code. This guide walks you through everything you need to automate passkey login testing, with ready-to-use TypeScript examples.


๐Ÿ” What Is WebAuthn and Why Should QA Engineers Care?

WebAuthn (Web Authentication) is the W3C standard behind passkeys. Instead of passwords, users authenticate with biometrics (fingerprint, face recognition) or hardware security keys.

Major platforms โ€” Google, Apple, Microsoft, and most modern SaaS products โ€” now support passkey login. As a QA engineer or SDET, you’ll increasingly encounter apps that use WebAuthn, and your automation framework needs to test these flows reliably.

โš ๏ธ The Problem: Traditional test tools couldn’t interact with hardware authenticators. You’d have to skip those tests or test them manually. Playwright 1.61 solves this completely with a built-in virtual authenticator!


๐Ÿ“‹ Prerequisites

Before we start, make sure you have:

  • โœ… Node.js 18 or later
  • โœ… Playwright 1.61+ installed
  • โœ… Basic knowledge of TypeScript and Playwright
# Install or upgrade Playwright
npm install @playwright/test@latest
npx playwright install

# Verify version
npx playwright --version

โš™๏ธ Setting Up the Virtual Authenticator

Playwright’s virtual authenticator is available through browserContext.credentials. No additional setup is required โ€” it’s built into the browser context.

import { test, expect } from '@playwright/test';

test('virtual authenticator is available', async ({ context }) => {
  // The credentials API is ready to use immediately
  const credentials = await context.credentials.list();
  expect(credentials).toEqual([]);
  console.log('Virtual authenticator is active!');
});

๐Ÿ’ก Key Insight: The virtual authenticator behaves like a FIDO2-compliant security key. When a website triggers a WebAuthn ceremony (registration or login), Playwright’s authenticator responds automatically โ€” no user interaction needed!


๐Ÿ”‘ Registering a Passkey in Your Tests

Here’s how to automate passkey registration:

import { test, expect } from '@playwright/test';

test('register a new passkey', async ({ context, page }) => {
  // Navigate to your app's registration page
  await page.goto('https://your-app.com/settings/security');

  // Click the "Add Passkey" button
  await page.getByRole('button', { name: 'Add Passkey' }).click();

  // Playwright's virtual authenticator handles the WebAuthn ceremony
  // Wait for confirmation
  await expect(page.getByText('Passkey added successfully')).toBeVisible();

  // Verify the credential was stored
  const credentials = await context.credentials.list();
  expect(credentials.length).toBe(1);
  console.log('Credential ID:', credentials[0].id);
});

When the browser triggers navigator.credentials.create(), the virtual authenticator generates a key pair and responds with the public key credential โ€” exactly as a real hardware key would.


๐Ÿš€ Automating Passkey Login

Once a passkey is registered, testing the login flow is straightforward:

test('login with passkey', async ({ context, page }) => {
  // Step 1: Register a passkey
  await page.goto('https://your-app.com/register');
  await page.getByRole('button', { name: 'Register with Passkey' }).click();
  await expect(page.getByText('Passkey registered')).toBeVisible();

  // Step 2: Navigate to login
  await page.goto('https://your-app.com/login');

  // Step 3: Sign in with passkey
  await page.getByRole('button', { name: 'Sign in with Passkey' }).click();

  // The virtual authenticator uses the stored credential
  await expect(page.getByText('Welcome back')).toBeVisible();
  await expect(page).toHaveURL(/dashboard/);
});

โœ… No mocking, no bypassing โ€” you’re testing the REAL WebAuthn implementation end to end. This is the biggest advantage over traditional approaches!


โ™ป๏ธ Reusing Credentials Across Tests with Storage State

Registering a passkey in every test is wasteful. Use Playwright’s storage state to authenticate once and reuse:

// auth.setup.ts
import { test as setup } from '@playwright/test';

setup('register and authenticate with passkey', async ({ context, page }) => {
  await page.goto('https://your-app.com/register');
  await page.getByRole('button', { name: 'Register with Passkey' }).click();
  await expect(page.getByText('Passkey registered')).toBeVisible();

  // Save authenticated state
  await context.storageState({ path: '.auth/passkey-user.json' });
});
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'tests',
      dependencies: ['setup'],
      use: { storageState: '.auth/passkey-user.json' },
    },
  ],
});

๐Ÿšจ Common Mistakes and How to Avoid Them

โŒ Mistake 1: Credentials don’t persist across browser contexts.

Each browserContext has its own virtual authenticator. If your test creates a new context, previously registered passkeys won’t be available. Solution: register and login within the same context, or use storage state.

โŒ Mistake 2: Not waiting for the WebAuthn ceremony to complete.

The registration and authentication ceremonies are asynchronous. Clicking the next button before the ceremony finishes causes flaky tests. Always wait for a visible confirmation element.

โŒ Mistake 3: Forgetting to await credentials API calls.

context.credentials.list() returns a Promise. Missing await leads to empty results and confusing test failures.


โ“ FAQ

๐Ÿค” Can I test passkeys without a real WebAuthn-enabled app?

Yes! You can use demo sites like webauthn.io to practice. Our practice automation page also supports WebAuthn flows.


๐Ÿค” Does Playwright’s virtual authenticator work with all browsers?

The virtual authenticator works with Chromium-based browsers. Firefox and WebKit support may vary โ€” check the Playwright 1.61 release notes for the latest compatibility.


๐Ÿค” Can I export and import passkey credentials between test runs?

Yes, context.credentials.list() returns credential objects that can be serialized. However, using storage state (shown above) is the recommended approach for persisting authenticated sessions.


๐Ÿค” What’s the difference between passkeys and FIDO2 security keys?

Passkeys are discoverable FIDO2 credentials synced across devices (through iCloud Keychain, Google Password Manager, etc.). Hardware security keys are physical devices. Playwright’s virtual authenticator simulates both.


๐Ÿค” Do I need to modify my app’s code to test with the virtual authenticator?

No. The virtual authenticator intercepts standard WebAuthn API calls at the browser level. Your app’s code doesn’t need any changes.


๐ŸŽฏ Next Steps

Now that you can automate passkey testing, try these:

  • ๐Ÿ”ง Add passkey tests to your CI/CD pipeline
  • ๐Ÿงช Test error scenarios (expired credentials, wrong authenticator)
  • ๐Ÿ“ Combine passkey auth with your existing Page Object Model โ€” see our Playwright POM tutorial
  • ๐Ÿ’พ Explore the new page.localStorage and page.sessionStorage APIs in Playwright 1.61

๐ŸŽฌ Want to see this in action? Watch the full video tutorial!

Subscribe to SqaToolsByDY on YouTube for weekly QA automation tutorials

๐ŸŒ Practice on sqatools.in โ€” free automation practice pages!