JavaScript Object Programs (Without Solution)

The following exercises are designed for developers who already understand JavaScript objects and want to practice solving real-world problems. Each program includes a scenario, input, expected output, and requirements without providing the solution.


1. Find the Employee with the Highest Salary

Scenario

A company stores employee details in an object. Find the employee who has the highest salary.

Input

const employees = {
    emp1: { name: "John", salary: 55000 },
    emp2: { name: "Alice", salary: 72000 },
    emp3: { name: "David", salary: 68000 }
};

Expected Output

Highest Salary Employee:
Alice
Salary: 72000

2. Count Product Categories

Scenario

An online shopping website stores products with categories. Count how many products belong to each category.

Input

const products = {
    p1: { category: "Electronics" },
    p2: { category: "Furniture" },
    p3: { category: "Electronics" },
    p4: { category: "Books" },
    p5: { category: "Books" }
};

Expected Output

Electronics : 2
Furniture : 1
Books : 2

3. Merge Student Information

Scenario

Merge two student objects into one object.

Input

const personal = {
    name: "Rahul",
    age: 21
};

const academic = {
    course: "B.Tech",
    marks: 88
};

Expected Output

{
    name: "Rahul",
    age: 21,
    course: "B.Tech",
    marks: 88
}

4. Find Missing Properties

Scenario

Check whether every employee object contains an email property.

Input

const employees = {
    emp1: {
        name: "John",
        email: "john@test.com"
    },
    emp2: {
        name: "Alice"
    },
    emp3: {
        name: "David",
        email: "david@test.com"
    }
};

Expected Output

Employee Missing Email:
Alice

5. Calculate Total Shopping Cart Value

Scenario

Calculate the total bill of all products in the shopping cart.

Input

const cart = {
    item1: {
        name: "Mouse",
        price: 700,
        quantity: 2
    },
    item2: {
        name: "Keyboard",
        price: 1200,
        quantity: 1
    },
    item3: {
        name: "Monitor",
        price: 9500,
        quantity: 1
    }
};

Expected Output

Total Cart Value:
12100

6. Remove Null Values from Object

Scenario

Remove all properties whose value is null.

Input

const user = {
    name: "Deepesh",
    phone: null,
    city: "Bhopal",
    email: null,
    age: 30
};

Expected Output

{
    name: "Deepesh",
    city: "Bhopal",
    age: 30
}

7. Find Duplicate Values

Scenario

Identify duplicate values present in an object.

Input

const students = {
    s1: "A",
    s2: "B",
    s3: "A",
    s4: "C",
    s5: "B"
};

Expected Output

Duplicate Values:
A
B

8. Convert Object into Sorted Array

Scenario

Convert the object values into an array and sort them in ascending order.

Input

const marks = {
    maths: 78,
    science: 91,
    english: 65,
    computer: 99
};

Expected Output

[65, 78, 91, 99]

9. Update Nested Object

Scenario

Update the city of the employee.

Input

const employee = {
    id: 101,
    name: "John",
    address: {
        city: "Delhi",
        state: "Delhi"
    }
};

Task

Update city to Mumbai.

Expected Output

{
    id:101,
    name:"John",
    address:{
        city:"Mumbai",
        state:"Delhi"
    }
}

10. Find Average Salary

Scenario

Calculate the average salary of all employees.

Input

const employees = {
    emp1: { salary: 45000 },
    emp2: { salary: 60000 },
    emp3: { salary: 75000 },
    emp4: { salary: 50000 }
};

Expected Output

Average Salary:
57500

11. Inventory Stock Checker

Scenario

Display all products whose quantity is less than 5.

Input

const inventory = {
    p1: { name: "Laptop", quantity: 3 },
    p2: { name: "Keyboard", quantity: 8 },
    p3: { name: "Mouse", quantity: 2 },
    p4: { name: "Monitor", quantity: 10 }
};

Expected Output

Low Stock Products:
Laptop
Mouse

12. Group Employees by Department

Scenario

Group employees based on department.

Input

const employees = {
    emp1: { name: "John", department: "IT" },
    emp2: { name: "Alice", department: "HR" },
    emp3: { name: "David", department: "IT" },
    emp4: { name: "Emma", department: "Finance" }
};

Expected Output

{
    IT: ["John", "David"],
    HR: ["Alice"],
    Finance: ["Emma"]
}

13. Find the Most Expensive Product

Scenario

Find the product with the highest price.

Input

const products = {
    p1: { name: "Phone", price: 25000 },
    p2: { name: "Laptop", price: 65000 },
    p3: { name: "Watch", price: 12000 }
};

Expected Output

Laptop
65000

14. Count Boolean Values

Scenario

Count how many properties have true and false values.

Input

const permissions = {
    read: true,
    write: false,
    delete: true,
    update: false,
    share: true
};

Expected Output

True : 3
False : 2

15. Reverse Key-Value Pairs

Scenario

Swap the keys and values of an object.

Input

const countryCodes = {
    India: "IN",
    America: "US",
    Japan: "JP"
};

Expected Output

{
    IN: "India",
    US: "America",
    JP: "Japan"
}

Top 50 Playwright Assertions Interview Questions and Answers

Assertions are one of the most frequently asked topics in Playwright interviews. They help verify whether the application behaves as expected after performing user actions. Playwright provides a powerful assertion library with auto-waiting, retry mechanisms, and rich error reporting, making tests more reliable and less flaky.

This guide covers beginner, intermediate, and advanced Playwright assertion interview questions with detailed answers and TypeScript examples.


1. What are Assertions in Playwright?

Answer

Assertions are used to verify that the actual result matches the expected result. If the expected condition is not met, the test fails.

Example

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

test('Verify Page Title', async ({ page }) => {
    await page.goto('https://example.com');

    await expect(page).toHaveTitle('Example Domain');
});

2. Why are Assertions important in Automation Testing?

Answer

Assertions ensure that:

  • The application behaves correctly.
  • Test results are validated.
  • Bugs are detected automatically.
  • Expected UI and business logic are verified.

Without assertions, an automation script only performs actions without validating outcomes.


3. What assertion library does Playwright use?

Answer

Playwright Test provides a built-in expect() assertion library.

expect(value).toBe(expected);

4. What makes Playwright assertions better than traditional assertions?

Answer

Playwright assertions provide:

  • Auto-waiting
  • Automatic retries
  • Better error messages
  • Screenshot capture on failures
  • Trace support
  • Reduced flaky tests

5. What is expect() in Playwright?

Answer

expect() is used to validate expected results.

expect(10).toBe(10);

6. What is auto-waiting in Playwright assertions?

Answer

Playwright automatically waits until the expected condition becomes true or the timeout is reached.

Example:

await expect(page.locator('#login')).toBeVisible();

No explicit wait is required.


7. What happens if an assertion fails?

Answer

  • The current test fails.
  • Playwright captures useful diagnostics (such as screenshots and traces, if configured).
  • Remaining steps in the test are skipped unless soft assertions are used.

8. What is the default timeout for assertions?

Answer

By default, Playwright assertions use the configured expect timeout, which is 5 seconds unless changed in the Playwright configuration or overridden for a specific assertion.

Example:

await expect(locator).toBeVisible({
    timeout: 10000
});

9. Difference between toBe() and toEqual()?

Answer

toBe()toEqual()
Checks primitive values using strict equalityDeep comparison for objects and arrays
Best for numbers, strings, booleansBest for objects and arrays

Example:

expect(5).toBe(5);

expect({
    name: 'John'
}).toEqual({
    name: 'John'
});

10. What is not in Playwright assertions?

Answer

Used to verify negative conditions.

expect(5).not.toBe(10);

11. What is toBeTruthy()?

Answer

Checks whether a value is truthy.

expect(true).toBeTruthy();

12. What is toBeFalsy()?

Answer

Checks whether a value is falsy.

expect(false).toBeFalsy();

13. What is toBeNull()?

Answer

Checks if the value is null.

expect(null).toBeNull();

14. What is toBeDefined()?

Answer

Checks whether a variable is defined.

const username = 'Admin';

expect(username).toBeDefined();

15. What is toBeUndefined()?

Answer

Checks if a variable is undefined.

let city;

expect(city).toBeUndefined();

16. What is toContain()?

Answer

Checks whether an array or string contains a value.

expect('Playwright').toContain('wright');

17. What is toHaveLength()?

Answer

Verifies array or string length.

expect([1,2,3]).toHaveLength(3);

18. What is toBeGreaterThan()?

Answer

Checks if a value is greater than another value.

expect(100).toBeGreaterThan(50);

19. What is toBeLessThan()?

Answer

expect(10).toBeLessThan(20);

20. What is toBeCloseTo()?

Answer

Useful for floating-point numbers.

expect(0.1 + 0.2).toBeCloseTo(0.3);

21. What are Locator Assertions?

Answer

Locator assertions validate the state of UI elements.

Examples:

  • toBeVisible()
  • toBeHidden()
  • toBeEnabled()
  • toHaveText()
  • toHaveValue()

22. What is toBeVisible()?

Answer

Checks whether an element is visible.

await expect(
    page.locator('#login')
).toBeVisible();

23. What is toBeHidden()?

Answer

Checks if an element is hidden.

await expect(
    page.locator('.loader')
).toBeHidden();

24. What is toBeEnabled()?

Answer

Verifies that an element is enabled.

await expect(
    page.locator('#submit')
).toBeEnabled();

25. What is toBeDisabled()?

Answer

Checks if an element is disabled.

await expect(
    page.locator('#submit')
).toBeDisabled();

26. What is toBeChecked()?

Answer

Used for checkboxes and radio buttons.

await expect(
page.locator('#remember')
).toBeChecked();

27. What is toHaveText()?

Answer

Verifies exact text.

await expect(
page.locator('h1')
).toHaveText('Dashboard');

28. What is toContainText()?

Answer

Checks partial text.

await expect(
page.locator('.message')
).toContainText('Success');

29. Difference between toHaveText() and toContainText()?

Answer

toHaveText()toContainText()
Exact matchPartial match
Entire text must matchOnly a portion needs to match

30. What is toHaveValue()?

Answer

Checks the value of input fields.

await expect(
page.locator('#username')
).toHaveValue('Admin');

31. What is toHaveAttribute()?

Answer

Checks an HTML attribute.

await expect(
page.locator('#email')
).toHaveAttribute('type','email');

32. What is toHaveClass()?

Answer

Verifies CSS classes.

await expect(
page.locator('.active')
).toHaveClass('active');

33. What is toHaveCount()?

Answer

Checks the number of matching elements.

await expect(
page.locator('.product')
).toHaveCount(5);

34. What is toBeEditable()?

Answer

Checks if an input can be edited.

await expect(
page.locator('#username')
).toBeEditable();

35. What is toHaveCSS()?

Answer

Verifies CSS property values.

await expect(
page.locator('#title')
).toHaveCSS('color','rgb(255, 0, 0)');

36. What are Page Assertions?

Answer

Assertions used directly on the page.

Examples:

  • toHaveTitle()
  • toHaveURL()

37. What is toHaveTitle()?

Answer

Checks the page title.

await expect(page)
.toHaveTitle('Dashboard');

38. What is toHaveURL()?

Answer

Checks the current URL.

await expect(page)
.toHaveURL(/dashboard/);

39. What are Soft Assertions?

Answer

Soft assertions allow the test to continue even if an assertion fails.

expect.soft(title).toBe('Dashboard');

expect.soft(username).toBe('Admin');

40. When should you use Soft Assertions?

Answer

Use soft assertions when:

  • Validating multiple UI elements.
  • Collecting all failures in a single execution.
  • Creating comprehensive UI verification tests.

41. What is expect.poll()?

Answer

expect.poll() repeatedly executes a function until the expected result is achieved or the timeout expires.

await expect.poll(async () => {
    return await page.locator('.counter').textContent();
}).toBe('10');

42. What is the difference between expect() and expect.poll()?

Answer

expect()expect.poll()
Checks an immediate value or locatorRepeatedly evaluates a callback until the condition is met
Best for UI elements and direct valuesBest for values that change over time

43. What are Screenshot Assertions?

Answer

Used for visual regression testing.

await expect(page)
.toHaveScreenshot();

44. Can assertions be customized with messages?

Answer

Yes.

expect(
    total,
    'Total price should be greater than zero'
).toBeGreaterThan(0);

45. How do you verify API responses in Playwright?

Answer

const response = await page.request.get(
'https://reqres.in/api/users/2'
);

expect(response.status()).toBe(200);

expect(response.ok()).toBeTruthy();

46. What are common mistakes when writing assertions?

Answer

  • Using waitForTimeout() before assertions.
  • Verifying text with textContent() instead of toHaveText().
  • Writing unnecessary manual waits.
  • Using exact matches for dynamic values.
  • Overusing hard-coded timeouts.

47. Why are locator assertions preferred over manual value checks?

Answer

Locator assertions automatically wait for the expected condition and retry until the timeout expires, making tests more stable and less flaky.

Instead of:

const text = await page.locator('h1').textContent();
expect(text).toBe('Dashboard');

Prefer:

await expect(page.locator('h1')).toHaveText('Dashboard');

48. Can Playwright assertions be used with regular JavaScript variables?

Answer

Yes.

const total = 100;

expect(total).toBe(100);

49. How do you verify multiple conditions in one test?

Answer

await expect(page).toHaveTitle('Dashboard');

await expect(page).toHaveURL(/dashboard/);

await expect(page.locator('#logout'))
    .toBeVisible();

await expect(page.locator('.product'))
    .toHaveCount(5);

50. What are the best practices for Playwright Assertions?

Answer

  • Use locator assertions (toBeVisible(), toHaveText(), toHaveValue()) instead of manually reading values.
  • Avoid waitForTimeout(); rely on Playwright’s auto-waiting.
  • Use expect.soft() when multiple independent validations should run in the same test.
  • Use expect.poll() for asynchronous values that change over time.
  • Keep assertions specific and focused on a single expected outcome.
  • Use regular expressions for dynamic URLs and text where appropriate.
  • Write descriptive custom messages for critical business validations.
  • Prefer built-in Playwright assertions over custom validation logic whenever possible.

Bonus Interview Questions

1. What is the difference between expect(locator).toHaveText() and expect(await locator.textContent()).toBe()?

Answer:

toHaveText() automatically waits and retries until the expected text appears, making it more reliable for dynamic web pages. textContent() retrieves the current text immediately and does not retry.


2. Why should waitForTimeout() not be used before assertions?

Answer:

waitForTimeout() introduces unnecessary delays and can make tests flaky. Playwright assertions already include built-in waiting and retry mechanisms, so explicit sleep statements are rarely needed.


3. Which Playwright assertions are most commonly asked in interviews?

Answer:

The most frequently discussed assertions are:

  • toBe()
  • toEqual()
  • toContain()
  • toHaveText()
  • toContainText()
  • toBeVisible()
  • toBeHidden()
  • toBeEnabled()
  • toBeDisabled()
  • toHaveValue()
  • toHaveAttribute()
  • toHaveCount()
  • toHaveURL()
  • toHaveTitle()
  • expect.soft()
  • expect.poll()
  • toHaveScreenshot()

Mastering these assertions and understanding when to use each one will prepare you for most Playwright automation interviews, from beginner to advanced levels.

Top 50 Playwright Locator Interview Questions and Answers

Playwright locators are one of the most important interview topics for QA Automation Engineers, SDETs, and Test Automation Engineers. Interviewers frequently ask questions about locator strategies, best practices, auto-waiting, strict mode, and advanced locator chaining.


1. What is a Locator in Playwright?

Answer:

A locator is an object that identifies and interacts with elements on a web page. Unlike traditional selectors, Playwright locators automatically wait for elements to become available before performing actions.

Example

const loginButton = page.locator('#login');

await loginButton.click();

2. Why are Playwright Locators better than CSS or XPath?

Answer:

Playwright locators provide:

  • Auto-waiting
  • Retry mechanism
  • Better readability
  • Improved stability
  • Built-in strict mode
  • Better handling of dynamic pages

3. What is the difference between page.locator() and page.$()?

Answer:

page.locator()page.$()
Auto waitsNo auto wait
RecommendedDeprecated for most use cases
Supports retriesReturns immediately
Better stabilityMore flaky

Example:

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

4. What is Strict Mode in Playwright?

Answer:

Strict mode ensures a locator resolves to exactly one element.

If multiple elements match, Playwright throws an error.

Example

await page.locator('button').click();

Error:

Strict mode violation:
Locator resolved to 5 elements.

5. How do you disable Strict Mode?

Answer:

Use methods like:

.first()

.last()

.nth(index)

Example

await page.locator('button').first().click();

6. Explain page.locator().

Answer:

Creates a reusable locator.

const username = page.locator('#username');

await username.fill('Admin');

7. What is getByRole()?

Answer:

Locates elements using their ARIA role.

Example

await page.getByRole('button', {
    name: 'Login'
}).click();

Recommended because it mimics user interaction.


8. Why is getByRole() preferred?

Answer:

  • Accessibility friendly
  • Stable
  • Less affected by UI changes
  • Easy to understand

9. What is getByText()?

Answer:

Finds elements using visible text.

await page.getByText('Sign In').click();

10. Difference between locator('text=Login') and getByText()?

Answer:

getByText() is more readable and recommended.

page.getByText('Login');

instead of

page.locator('text=Login');

11. What is getByLabel()?

Answer:

Locates form fields using associated labels.

await page.getByLabel('Username')
.fill('Admin');

12. What is getByPlaceholder()?

Answer:

Finds input elements using placeholder text.

await page
.getByPlaceholder('Search')
.fill('Laptop');

13. What is getByAltText()?

Answer:

Used for images.

page.getByAltText('Company Logo');

14. What is getByTitle()?

Answer:

Finds elements using the title attribute.

page.getByTitle('Settings');

15. What is getByTestId()?

Answer:

Locates elements using the data-testid attribute.

<button data-testid="login-btn">
page.getByTestId('login-btn');

16. Why is data-testid recommended?

Answer:

  • Independent of UI
  • Stable
  • Easy to maintain
  • Designed for automation

17. What is Locator Chaining?

Answer:

Locating elements inside another locator.

page.locator('.card')
.locator('button');

18. What is filter()?

Answer:

Filters locator results.

page.locator('.product')
.filter({
    hasText:'Laptop'
});

19. What is hasText?

Answer:

Filters elements by text.

page.locator('li').filter({
    hasText:'Apple'
});

20. What is has()?

Answer:

Filters elements containing another locator.

page.locator('.card').filter({

    has: page.locator('button')

});

21. What is nth()?

Answer:

Selects the element at a given index (zero-based).

page.locator('li').nth(2);

22. What is first()?

Answer:

Returns the first matching element.

page.locator('button').first();

23. What is last()?

Answer:

Returns the last matching element.

page.locator('button').last();

24. Difference between nth(0) and first()?

Answer:

Both return the first element, but first() is more readable and expressive.


25. How do you count matching elements?

Answer:

const count =
await page.locator('.product').count();

26. How do you loop through multiple elements?

Answer:

const items = page.locator('.product');

const count = await items.count();

for (let i = 0; i < count; i++) {
    console.log(await items.nth(i).textContent());
}

27. How do you get the text of an element?

Answer:

const text =
await page.locator('h1').textContent();

Prefer assertions when verifying text:

await expect(page.locator('h1'))
    .toHaveText('Dashboard');

28. What is allTextContents()?

Answer:

Returns the text content of all matching elements.

const texts =
await page.locator('li').allTextContents();

29. What is allInnerTexts()?

Answer:

Returns the rendered inner text of all matching elements, excluding hidden text in many cases.

const texts =
await page.locator('li').allInnerTexts();

30. Difference between textContent() and innerText()?

Answer:

textContent()innerText()
Includes hidden textReturns visible rendered text
FasterSlower due to layout calculations
Reads DOM textReads displayed text

31. How do you locate using CSS selectors?

Answer:

page.locator('#username');

page.locator('.login');

page.locator('input');

32. Can Playwright use XPath?

Answer:

Yes.

page.locator('//button');

However, CSS selectors and user-facing locators like getByRole() are generally preferred for readability and maintainability.


33. How do you locate a parent element?

Answer:

Use locator chaining or XPath when appropriate.

Example using chaining:

const card = page.locator('.card').filter({
  has: page.getByText('Product A')
});

34. How do you locate child elements?

Answer:

page.locator('.card')
.locator('button');

35. What is Auto Waiting?

Answer:

Playwright automatically waits until the target element is actionable (e.g., attached, visible, stable, and enabled where applicable) before performing actions.

Example:

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

36. Can Locators be reused?

Answer:

Yes.

const loginButton =
page.getByRole('button', { name: 'Login' });

await loginButton.click();
await expect(loginButton).toBeVisible();

37. Are Locators Lazy?

Answer:

Yes.

A locator does not immediately search the DOM. It resolves the element when an action or assertion is performed, allowing it to work with dynamic pages.


38. Difference between Locator and ElementHandle?

Answer:

LocatorElementHandle
Lazy evaluationSnapshot of a DOM element
Auto waitsNo automatic retries
RecommendedUse only for advanced scenarios
More resilientCan become stale

39. How do you wait for a Locator?

Answer:

Usually you don’t need to wait manually.

If needed:

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

40. How do you verify a Locator exists?

Answer:

await expect(
page.locator('#login')
).toBeVisible();

41. How do you locate an element inside an iframe?

Answer:

Use frameLocator().

await page
.frameLocator('#payment-frame')
.getByRole('button', { name: 'Pay Now' })
.click();

42. How do you locate shadow DOM elements?

Answer:

Playwright automatically pierces open shadow DOMs, so standard locators often work without extra APIs.

await page.getByRole('button', { name: 'Submit' }).click();

43. What are the best locator strategies in Playwright?

Answer:

Preferred order:

  1. getByRole()
  2. getByLabel()
  3. getByPlaceholder()
  4. getByTestId()
  5. getByText()
  6. CSS selectors
  7. XPath (only when necessary)

44. Why should XPath be avoided when possible?

Answer:

  • More fragile
  • Harder to read
  • Breaks easily after DOM changes
  • Longer and more complex expressions

45. How do you handle dynamic locators?

Answer:

Use template literals.

const productName = 'Laptop';

await page.getByText(productName).click();

Or:

await page.locator(
    `[data-id="${productName}"]`
).click();

46. How do you locate a button with specific text?

Answer:

await page.getByRole('button', {
    name: 'Submit'
}).click();

47. How do you locate multiple buttons with the same text?

Answer:

Use nth(), first(), or last().

await page
.getByRole('button', { name: 'Edit' })
.nth(1)
.click();

48. How do you debug a locator?

Answer:

Useful techniques include:

  • Use Playwright Inspector (--debug)
  • Use page.pause()
  • Use the Playwright Codegen tool
  • Inspect the locator in browser DevTools
  • Verify uniqueness with assertions like toHaveCount(1)

Example:

await page.pause();

49. What are common locator mistakes?

Answer:

  • Using overly generic selectors like div or button
  • Relying on dynamic CSS classes
  • Depending on long XPath expressions
  • Using nth() unnecessarily when a unique locator is available
  • Adding manual waits instead of relying on locator auto-waiting

50. What are the best practices for Playwright locators?

Answer:

  • Prefer user-facing locators such as getByRole() and getByLabel().
  • Use data-testid for elements without stable accessible attributes.
  • Create reusable locators in Page Object Model classes.
  • Avoid brittle XPath and deeply nested CSS selectors.
  • Keep locators unique and descriptive.
  • Use Playwright assertions (toBeVisible(), toHaveText(), etc.) instead of manual checks.
  • Take advantage of locator chaining and filter() to make selectors more precise.
  • Write locators that reflect user behavior rather than implementation details.

Bonus Interview Question

What is the recommended locator priority in Playwright?

Answer:

The Playwright team recommends choosing locators in roughly this order:

  1. getByRole() – Best for accessibility and user-centric testing.
  2. getByLabel() – Ideal for form controls.
  3. getByPlaceholder() – Useful for inputs with meaningful placeholders.
  4. getByTestId() – Great for stable automation-specific selectors.
  5. getByText() – Suitable for visible text when it uniquely identifies the element.
  6. CSS selectors – Use when semantic locators are unavailable.
  7. XPath – Reserve for cases where no better locator strategy is feasible.

Following this hierarchy produces tests that are more readable, accessible, and resilient to UI changes.

Playwright Assertions with TypeScript – Complete Guide with Examples

Assertions are one of the most important concepts in Playwright test automation. They verify whether the application behaves as expected after performing an action. Without assertions, your automation script simply performs actions without validating the results.

Playwright provides a powerful assertion library built on top of expect(), which automatically waits for conditions to become true before failing the test. This feature makes Playwright tests much more reliable compared to traditional automation frameworks.

In this article, you’ll learn everything about Playwright assertions, including different assertion types, best practices, and practical examples using TypeScript.


What are Assertions?

Assertions verify the expected state of your application.

For example, after clicking the Login button, you may want to verify:

  • User is redirected to Dashboard
  • Welcome message is displayed
  • Logout button is visible
  • Shopping cart contains 3 products

If the expected condition is not met, the test fails.

Example:

expect(await page.title()).toBe("Dashboard");

Why Playwright Assertions are Powerful

Unlike traditional assertions, Playwright assertions:

  • Auto-wait until the condition becomes true
  • Retry assertions automatically
  • Produce detailed error messages
  • Capture screenshots on failures
  • Generate trace files
  • Work with UI elements directly

Traditional Selenium example:

Thread.sleep(5000);
Assert.assertEquals(driver.getTitle(), "Dashboard");

Playwright:

await expect(page).toHaveTitle("Dashboard");

No manual waits required.


Importing Assertions

Playwright Test automatically provides expect.

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

Assertion Categories

Playwright provides several assertion types.

  • Value Assertions
  • Locator Assertions
  • Page Assertions
  • API Assertions
  • Screenshot Assertions
  • Soft Assertions
  • Polling Assertions
  • Custom Assertions

Let’s explore each one.


1. Value Assertions

Used for JavaScript values.

Example:

test('Number Assertion', async () => {

    const count = 10;

    expect(count).toBe(10);

});

toBe()

Checks exact equality.

expect(5).toBe(5);

Example

const user = "Deepesh";

expect(user).toBe("Deepesh");

not.toBe()

expect(5).not.toBe(10);

toEqual()

Used for arrays and objects.

const user = {
    name: "John",
    age: 30
};

expect(user).toEqual({
    name: "John",
    age: 30
});

toContain()

expect(["Apple","Orange"]).toContain("Apple");

String example

expect("Playwright Automation").toContain("Automation");

toHaveLength()

expect([1,2,3]).toHaveLength(3);

String

expect("Playwright").toHaveLength(10);

toBeTruthy()

expect(true).toBeTruthy();

Example

const isLoggedIn = true;

expect(isLoggedIn).toBeTruthy();

toBeFalsy()

expect(false).toBeFalsy();

toBeNull()

expect(null).toBeNull();

toBeDefined()

const username = "Admin";

expect(username).toBeDefined();

toBeUndefined()

let city;

expect(city).toBeUndefined();

toBeGreaterThan()

expect(20).toBeGreaterThan(10);

toBeLessThan()

expect(5).toBeLessThan(10);

toBeCloseTo()

Useful for decimal values.

expect(0.1 + 0.2).toBeCloseTo(0.3);

2. Locator Assertions

Locator assertions are the most commonly used assertions in Playwright.

Example locator

const loginButton = page.locator("#login");

toBeVisible()

Checks whether an element is visible.

await expect(loginButton).toBeVisible();

toBeHidden()

await expect(page.locator(".loader")).toBeHidden();

toBeEnabled()

await expect(loginButton).toBeEnabled();

toBeDisabled()

await expect(page.locator("#submit")).toBeDisabled();

toBeChecked()

Checkbox example

await page.locator("#remember").check();

await expect(page.locator("#remember")).toBeChecked();

toHaveText()

await expect(page.locator("h1"))
.toHaveText("Dashboard");

toContainText()

await expect(page.locator(".message"))
.toContainText("Success");

toHaveValue()

Textbox example

await page.fill("#username","Admin");

await expect(page.locator("#username"))
.toHaveValue("Admin");

toHaveAttribute()

await expect(page.locator("#email"))
.toHaveAttribute("type","email");

toHaveClass()

await expect(page.locator(".active"))
.toHaveClass("active");

Multiple classes

await expect(page.locator(".btn"))
.toHaveClass(/btn primary/);

toHaveCount()

Useful for lists.

await expect(page.locator(".product"))
.toHaveCount(8);

toBeEditable()

await expect(page.locator("#username"))
.toBeEditable();

toHaveCSS()

await expect(page.locator("#title"))
.toHaveCSS("color","rgb(255, 0, 0)");

toHaveJSProperty()

await expect(page.locator("#checkbox"))
.toHaveJSProperty("checked", true);

toHaveId()

await expect(page.locator("#login"))
.toHaveId("login");

3. Page Assertions


toHaveTitle()

await expect(page)
.toHaveTitle("Dashboard");

toHaveURL()

await expect(page)
.toHaveURL("https://example.com/dashboard");

Using Regex

await expect(page)
.toHaveURL(/dashboard/);

4. Screenshot Assertions

Playwright can compare screenshots automatically.

await expect(page)
.toHaveScreenshot();

Specific filename

await expect(page)
.toHaveScreenshot("homepage.png");

Element screenshot

await expect(page.locator(".logo"))
.toHaveScreenshot();

This is useful for visual regression testing.


5. API Assertions

Example

const response = await page.request.get(
    "https://reqres.in/api/users/2"
);

expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy();

Verify JSON

const body = await response.json();

expect(body.data.first_name)
.toBe("Janet");

6. Soft Assertions

Normally,

expect(value).toBe(expected);

stops execution when it fails.

Soft assertions allow the test to continue.

expect.soft(title).toBe("Dashboard");

expect.soft(username).toBe("Admin");

Example

test('Soft Assertions', async ({ page }) => {

    await page.goto("https://example.com");

    await expect.soft(page).toHaveTitle("Home");

    await expect.soft(page.locator("h1"))
    .toContainText("Example");

    await expect.soft(page.locator("#login"))
    .toBeVisible();

});

7. Polling Assertions

Sometimes values are updated after several seconds.

Playwright provides polling.

await expect.poll(async () => {

    return await page.locator(".counter").textContent();

}).toBe("10");

Playwright retries until timeout.


8. Custom Message

Provide a helpful failure message.

expect(
    totalPrice,
    "Total price should be greater than zero"
).toBeGreaterThan(0);

9. Array Assertions

const fruits = [
    "Apple",
    "Orange",
    "Mango"
];

expect(fruits).toContain("Orange");
expect(fruits).toHaveLength(3);

10. Object Assertions

const employee = {

    id:1,
    name:"John"

};

expect(employee).toEqual({

    id:1,
    name:"John"

});

Real Login Example

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

test('Verify Login', async ({ page }) => {

    await page.goto("https://example.com");

    await page.fill("#username","Admin");

    await page.fill("#password","admin123");

    await page.click("#login");

    await expect(page)
        .toHaveURL(/dashboard/);

    await expect(page.locator("h1"))
        .toContainText("Dashboard");

    await expect(page.locator("#logout"))
        .toBeVisible();

});

Assertion Timeouts

By default, Playwright retries assertions until the configured timeout.

Example:

await expect(locator).toBeVisible({
    timeout: 10000
});

This waits up to 10 seconds before failing.


Combining Assertions

await expect(page).toHaveTitle("Dashboard");

await expect(page).toHaveURL(/dashboard/);

await expect(page.locator("#welcome"))
    .toContainText("Welcome");

await expect(page.locator("#logout"))
    .toBeVisible();

await expect(page.locator(".product"))
    .toHaveCount(6);

Best Practices

  • Prefer Playwright locator assertions (toBeVisible, toHaveText, etc.) over manual value checks because they automatically wait for the expected condition.
  • Avoid using waitForTimeout() before assertions. Let Playwright’s built-in retry mechanism handle synchronization.
  • Use descriptive locators (getByRole, getByLabel, getByTestId) instead of fragile CSS or XPath selectors whenever possible.
  • Use expect.soft() when you want to collect multiple verification failures in a single test execution.
  • Keep assertions focused on one expected behavior to make failures easier to understand.
  • Add meaningful custom messages for business-critical validations.
  • Use regular expressions with toHaveURL() and toHaveText() when parts of the value are dynamic.
  • For visual testing, use screenshot assertions only for stable UI components to reduce flaky results.
  • Set assertion-specific timeouts only when an element legitimately requires more time to appear.

Common Mistakes

Using Manual Waits

await page.waitForTimeout(5000);

expect(await page.title()).toBe("Dashboard");

await expect(page).toHaveTitle("Dashboard");

Using textContent() Instead of Locator Assertions

const text = await page.locator("h1").textContent();

expect(text).toBe("Dashboard");

await expect(page.locator("h1"))
    .toHaveText("Dashboard");

Hard-Coding Dynamic URLs

await expect(page).toHaveURL(
    "https://example.com/dashboard?id=123"
);

await expect(page).toHaveURL(/dashboard/);

Summary

Playwright assertions are a core part of building stable and maintainable automated tests. Their automatic waiting, intelligent retries, and rich set of locator-specific assertions eliminate much of the flakiness found in traditional UI automation frameworks.

By mastering value assertions, locator assertions, page assertions, API assertions, soft assertions, polling, and visual assertions, you can create robust end-to-end tests that are easier to read, debug, and maintain. Adopting Playwright’s built-in assertion methods instead of manual waits or custom validation logic will result in faster, more reliable, and more professional automation suites.

Playwright Locators with TypeScript – Complete Guide for Beginners to Advanced

Introduction

Locators are one of the most important concepts in Playwright. A locator is used to find and interact with elements on a web page. Unlike traditional Selenium locators, Playwright locators are smart, reliable, auto-waiting, and resilient.

Playwright automatically waits for elements to become:

  • Visible
  • Stable
  • Enabled
  • Ready for interaction

This significantly reduces flaky test cases.


What is a Locator?

A locator is an object that identifies one or more elements on a web page.

Example:

const username = page.locator('#username');

The locator does not immediately search for the element.

Instead, Playwright waits until the action is performed.

Example:

await page.locator('#username').fill('Admin');

Why Use Playwright Locators?

Advantages include:

  • Auto waiting
  • Better stability
  • Retry mechanism
  • Cleaner syntax
  • Supports chaining
  • Easy filtering
  • Works well with dynamic pages
  • Recommended by Playwright team

Basic Syntax

const locator = page.locator('selector');

Example

await page.locator('#email').fill('test@test.com');

Types of Playwright Locators

Playwright provides multiple locator strategies.

1. getByRole()

Recommended by Playwright.

Best for accessible applications.

Example

await page.getByRole('button', {
    name: 'Login'
}).click();

HTML

<button>Login</button>

Common Roles

RoleExample
buttonLogin Button
textboxInput field
checkboxRemember Me
radioGender
headingH1-H6
linkHome
comboboxDropdown
menuitemMenu
dialogPopup
imgImages

Example

await page.getByRole('textbox').fill('Admin');

2. getByText()

Find element using visible text.

Example

await page.getByText('Submit').click();

Partial text

await page.getByText('Submit', {
    exact: false
}).click();

Exact text

await page.getByText('Submit', {
    exact: true
}).click();

3. getByLabel()

Best for form automation.

HTML

<label>Email</label>
<input>

Playwright

await page.getByLabel('Email').fill('abc@test.com');

4. getByPlaceholder()

Locate input by placeholder.

HTML

<input placeholder="Enter username">

Playwright

await page.getByPlaceholder('Enter username').fill('Admin');

5. getByAltText()

Locate images.

HTML

<img alt="Company Logo">

Playwright

await page.getByAltText('Company Logo').click();

6. getByTitle()

Locate using title attribute.

HTML

<button title="Delete Record">

Playwright

await page.getByTitle('Delete Record').click();

7. getByTestId()

Highly recommended for automation.

HTML

<button data-testid="loginBtn">

Playwright

await page.getByTestId('loginBtn').click();

Configure custom attribute

use: {
    testIdAttribute: 'data-test'
}

8. CSS Locator

await page.locator('#username');

Class

await page.locator('.btn-primary');

Attribute

await page.locator('[type="submit"]');

Multiple classes

await page.locator('.btn.primary.large');

9. XPath Locator

await page.locator('//button[text()="Login"]');

Relative XPath

await page.locator('//input[@id="email"]');

Although supported, CSS or Playwright-specific locators are generally preferred because they are easier to maintain.


10. ID Locator

await page.locator('#login');

11. Class Locator

await page.locator('.login-button');

12. Name Attribute

await page.locator('[name="username"]');

13. Attribute Locator

await page.locator('[placeholder="Search"]');

14. Text Selector

await page.locator('text=Login');

Locator Chaining

Locate child elements.

HTML

<div class="card">
    <button>Edit</button>
</div>

Playwright

await page
    .locator('.card')
    .locator('button')
    .click();

Filter Locator

await page.locator('.card').filter({
    hasText: 'Laptop'
}).click();

has()

Locate parent having child.

await page.locator('div', {
    has: page.locator('button')
});

hasText()

await page.locator('li', {
    hasText: 'India'
});

nth()

Locate by index.

await page.locator('.product').nth(0);

Second element

await page.locator('.product').nth(1);

first()

await page.locator('.item').first();

last()

await page.locator('.item').last();

Locator Count

const total = await page.locator('.row').count();

console.log(total);

Iterate Multiple Elements

const products = page.locator('.product');

const count = await products.count();

for (let i = 0; i < count; i++) {

    console.log(
        await products.nth(i).textContent()
    );

}

allTextContents()

const names =
await page.locator('.name').allTextContents();

console.log(names);

allInnerTexts()

const values =
await page.locator('.city').allInnerTexts();

Locator Actions

Click

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

Double Click

await page.locator('#save').dblclick();

Right Click

await page.locator('#menu').click({
    button: 'right'
});

Fill

await page.locator('#email').fill('abc@test.com');

Type Slowly

await page.locator('#email').pressSequentially(
    'Admin'
);

Clear Text

await page.locator('#email').clear();

Press Keyboard

await page.locator('#search').press('Enter');

Check Checkbox

await page.locator('#remember').check();

Uncheck

await page.locator('#remember').uncheck();

Hover

await page.locator('#menu').hover();

Focus

await page.locator('#email').focus();

Drag and Drop

await page.locator('#source')
.dragTo(
    page.locator('#target')
);

Locator Assertions

Visible

await expect(
page.locator('#login')
).toBeVisible();

Hidden

await expect(
page.locator('#popup')
).toBeHidden();

Enabled

await expect(
page.locator('#save')
).toBeEnabled();

Disabled

await expect(
page.locator('#save')
).toBeDisabled();

Checked

await expect(
page.locator('#remember')
).toBeChecked();

Contains Text

await expect(
page.locator('h1')
).toContainText('Dashboard');

Exact Text

await expect(
page.locator('h1')
).toHaveText('Dashboard');

Count

await expect(
page.locator('.row')
).toHaveCount(5);

Working with Multiple Locators

const rows = page.locator('table tbody tr');

for(let i = 0; i < await rows.count(); i++){

    console.log(
        await rows.nth(i).textContent()
    );

}

Strict Mode

Playwright locators are strict by default. If a locator matches more than one element, Playwright throws an error.

Example:

await page.getByRole('button').click();

Error:

Error:
Locator resolved to multiple elements

Solution:

await page.getByRole('button').nth(0).click();

or

await page.getByRole('button', {
    name: 'Login'
}).click();

Best Practices

  1. Prefer getByRole() for interactive elements.
  2. Use getByLabel() for form fields.
  3. Use getByTestId() for stable automation.
  4. Avoid brittle XPath expressions whenever possible.
  5. Use locator chaining to narrow searches.
  6. Keep selectors short, readable, and maintainable.
  7. Use assertions such as toBeVisible() before complex interactions when they improve readability.
  8. Avoid using fixed waits like waitForTimeout(). Rely on Playwright’s auto-waiting.
  9. Store commonly used locators inside Page Object Model (POM) classes.
  10. Avoid using .nth() unless the order of elements is guaranteed.

Locator Priority (Recommended Order)

PriorityLocatorRecommended
1getByRole()⭐⭐⭐⭐⭐
2getByLabel()⭐⭐⭐⭐⭐
3getByTestId()⭐⭐⭐⭐⭐
4getByPlaceholder()⭐⭐⭐⭐
5getByText()⭐⭐⭐⭐
6CSS Selectors⭐⭐⭐
7XPath⭐⭐

Common Mistakes

  • Using long and fragile XPath expressions.
  • Depending on dynamically generated CSS classes.
  • Selecting elements by index when a unique locator is available.
  • Using waitForTimeout() instead of Playwright’s built-in waiting.
  • Ignoring accessibility-based locators such as getByRole() and getByLabel().
  • Repeating the same locator across multiple test files instead of centralizing them in Page Objects.

Real-World Login Example

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

test('User Login', async ({ page }) => {
    await page.goto('https://example.com/login');

    await page.getByLabel('Username').fill('admin');
    await page.getByLabel('Password').fill('admin123');
    await page.getByRole('button', { name: 'Login' }).click();

    await expect(
        page.getByRole('heading', { name: 'Dashboard' })
    ).toBeVisible();
});

Summary

Playwright locators are designed to create reliable, readable, and maintainable UI automation tests. Instead of relying on fragile selectors, Playwright encourages the use of accessibility-aware locators such as getByRole(), getByLabel(), and getByTestId(). By combining locator chaining, filtering, built-in assertions, and auto-waiting, you can build test suites that are less flaky and easier to maintain over time.

Mastering Playwright locators is a foundational skill for any automation engineer and is essential for developing scalable Playwright frameworks using TypeScript.

JavaScript Array Scenario-Based Assignments (Without Solution)

1. Student Marks Analysis

Scenario

A teacher has stored the marks of students in an array. Find the highest mark.

Input

[65, 78, 92, 55, 88]

Expected Output

Highest Marks: 92

2. Employee Salary Increment

Scenario

An HR department wants to increase every employee’s salary by 10%.

Input

[25000, 32000, 45000]

Expected Output

[27500, 35200, 49500]

3. Find Failed Students

Scenario

Students scoring less than 35 are considered failed.

Input

[90, 25, 67, 18, 45, 30]

Expected Output

[25, 18, 30]

4. Product Price Filter

Scenario

Display all products costing more than $100.

Input

[50, 120, 90, 300, 150]

Expected Output

[120, 300, 150]

5. Customer Shopping Bill

Scenario

Calculate the total bill amount.

Input

[250, 450, 300, 500]

Expected Output

Total Bill: 1500

6. Remove Duplicate Customer IDs

Scenario

Remove duplicate customer IDs from the array.

Input

[101, 102, 103, 101, 104, 102]

Expected Output

[101, 102, 103, 104]

7. Website Visitor Count

Scenario

Find the average number of daily visitors.

Input

[1200, 1400, 1300, 1500, 1700]

Expected Output

Average Visitors: 1420

8. Online Store Inventory

Scenario

Check whether Product ID 105 exists.

Input

[101, 102, 103, 104]

Expected Output

Product Not Found

9. Company Departments

Scenario

Sort department names alphabetically.

Input

["HR", "Testing", "Development", "Support"]

Expected Output

["Development", "HR", "Support", "Testing"]

10. Reverse Delivery Route

Scenario

Reverse the delivery route.

Input

["Delhi", "Agra", "Jaipur", "Mumbai"]

Expected Output

["Mumbai", "Jaipur", "Agra", "Delhi"]

11. Remove Cancelled Orders

Scenario

Remove order ID 103 from the array.

Input

[101, 102, 103, 104, 105]

Expected Output

[101, 102, 104, 105]

12. Add New Employee

Scenario

Add employee ID 106 to the employee list.

Input

[101, 102, 103]

Expected Output

[101, 102, 103, 106]

13. Highest Monthly Sales

Scenario

Find the maximum sales amount.

Input

[12000, 18000, 25000, 16000]

Expected Output

Highest Sales: 25000

14. Lowest Temperature

Scenario

Find the lowest recorded temperature.

Input

[34, 29, 41, 25, 38]

Expected Output

Lowest Temperature: 25

15. Even Product IDs

Scenario

Display only even product IDs.

Input

[101, 102, 103, 104, 105, 106]

Expected Output

[102, 104, 106]

16. Odd Invoice Numbers

Scenario

Display only odd invoice numbers.

Input

[5001, 5002, 5003, 5004]

Expected Output

[5001, 5003]

17. Positive Bank Transactions

Scenario

Display only deposit transactions.

Input

[-200, 500, -100, 700, 300]

Expected Output

[500, 700, 300]

18. Negative Bank Transactions

Scenario

Display only withdrawal transactions.

Input

[-500, 400, -200, 100]

Expected Output

[-500, -200]

19. Movie Ratings

Scenario

Count how many ratings are greater than or equal to 4.

Input

[5, 3, 4, 2, 5, 4]

Expected Output

Count: 4

20. Bus Seat Numbers

Scenario

Find whether seat number 18 is available.

Input

[10, 12, 14, 16, 18, 20]

Expected Output

Seat Available

21. Daily Expenses

Scenario

Find the total weekly expense.

Input

[500, 650, 400, 700, 300]

Expected Output

Total Expense: 2550

22. Cricket Scores

Scenario

Find the second-highest score.

Input

[55, 72, 89, 91, 68]

Expected Output

Second Highest Score: 89

23. Order Quantity

Scenario

Multiply every order quantity by 2 for a promotional offer.

Input

[5, 10, 15]

Expected Output

[10, 20, 30]

24. Library Books

Scenario

Count the total number of books.

Input

["Java", "Python", "JavaScript", "C++"]

Expected Output

Total Books: 4

25. Hospital Patients

Scenario

Display patient IDs greater than 200.

Input

[150, 180, 205, 210, 175]

Expected Output

[205, 210]

26. Electricity Bills

Scenario

Find all bills greater than $1,000.

Input

[850, 1200, 990, 1400, 600]

Expected Output

[1200, 1400]

27. Flight Ticket Prices

Scenario

Sort ticket prices in ascending order.

Input

[4500, 2200, 6800, 3500]

Expected Output

[2200, 3500, 4500, 6800]

28. Employee Attendance

Scenario

Count the number of days an employee was present (marked as true).

Input

[true, false, true, true, false, true]

Expected Output

Present Days: 4

29. Store Discount

Scenario

Apply a 20% discount to all product prices.

Input

[500, 1000, 1500]

Expected Output

[400, 800, 1200]

30. Olympic Scores

Scenario

Find all scores greater than or equal to 90.

Input

[78, 95, 88, 91, 84, 99]

Expected Output

[95, 91, 99]

Playwright with Docker and Kubernetes (Beginner to Advanced)

Modern organizations rarely execute Playwright tests directly on developers’ machines. Instead, they package the automation framework into Docker containers and execute them in Kubernetes clusters for scalability, consistency, and faster execution.

This chapter explains how to run Playwright in Docker and Kubernetes using industry best practices.


Table of Contents

  1. Why Docker?
  2. Why Kubernetes?
  3. Docker Architecture
  4. Installing Docker
  5. Running Playwright in Docker
  6. Creating Dockerfile
  7. Creating .dockerignore
  8. Docker Commands
  9. Docker Compose
  10. Running Playwright Reports
  11. Kubernetes Architecture
  12. Running Playwright on Kubernetes
  13. Kubernetes Deployment
  14. ConfigMap
  15. Secret Management
  16. Persistent Volumes
  17. Best Practices
  18. Interview Questions

What is Docker?

Docker is a containerization platform that packages an application with all of its dependencies.

Instead of:

Developer Machine

↓

Install Node

↓

Install Playwright

↓

Install Browsers

↓

Install Libraries

Docker packages everything together.


Why Use Docker?

Benefits

  • Same environment everywhere
  • No “works on my machine” issues
  • Easy deployment
  • Portable
  • Lightweight
  • Fast startup
  • CI/CD friendly

Traditional Execution

Developer Laptop

↓

Install Node

↓

Install Browser

↓

Install Dependencies

↓

Run Tests

Different developers may have different versions.


Docker Execution

Docker Image

↓

Node

↓

Playwright

↓

Chromium

↓

Firefox

↓

WebKit

↓

Automation Code

Everyone runs the exact same environment.


Docker Architecture

               Docker Engine
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
   Container 1  Container 2  Container 3
      Smoke      Regression      API

Each container is isolated.


Install Docker

Verify installation:

docker --version

Verify Docker Engine:

docker info

List images:

docker images

List running containers:

docker ps

Recommended Project Structure

PlaywrightFramework

│

├── tests/

├── pages/

├── fixtures/

├── utils/

├── playwright.config.ts

├── package.json

├── Dockerfile

├── docker-compose.yml

├── .dockerignore

└── README.md

Understanding Dockerfile

A Dockerfile contains instructions to build a Docker image.

Typical steps:

Base Image

↓

Copy Project

↓

Install Dependencies

↓

Install Browsers

↓

Execute Tests

Sample Dockerfile

FROM mcr.microsoft.com/playwright:v1.55.0-jammy

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

CMD ["npx", "playwright", "test"]

Explanation

FROM

Uses Microsoft’s official Playwright image, which already includes:

  • Node.js
  • Playwright
  • Supported browsers
  • Required Linux libraries

WORKDIR /app

Creates the working directory.


COPY package*.json ./

Copies package files first to improve Docker layer caching.


RUN npm ci

Installs dependencies exactly as defined in package-lock.json.

Use npm ci in CI/CD rather than npm install because it is faster and more deterministic.


COPY . .

Copies project files.


CMD

Runs Playwright tests.


Build Docker Image

docker build -t playwright-framework .

Explanation

docker build

↓

Create Image

↓

Tag

↓

playwright-framework

Verify Images

docker images

Example

REPOSITORY              TAG

playwright-framework    latest

Run Container

docker run playwright-framework

Run in Interactive Mode

docker run -it playwright-framework

Useful for debugging.


Mount Local Directory

docker run -v ${PWD}:/app playwright-framework

This keeps project files synchronized between the host and the container.


Run Specific Test

docker run playwright-framework npx playwright test tests/login.spec.ts

Pass Environment Variables

docker run \
-e ENV=QA \
-e USERNAME=admin \
playwright-framework

Avoid hardcoding credentials in the image.


.dockerignore

Just like .gitignore, Docker ignores unnecessary files.

Example

node_modules

playwright-report

test-results

.git

.vscode

Benefits

  • Smaller images
  • Faster builds
  • Better performance

Docker Layers

Base Image

↓

Node Modules

↓

Project Files

↓

Automation Code

Docker caches unchanged layers, reducing build times.


Docker Compose

Docker Compose manages multiple containers.

Example

Playwright

↓

Application

↓

Database

↓

API

Sample docker-compose.yml

version: "3.9"

services:
  playwright:
    build: .
    container_name: playwright-tests
    command: npx playwright test
    volumes:
      - .:/app

Run:

docker compose up

Parallel Containers

Instead of

One Container

↓

1000 Tests

Use

Container 1

Smoke

----------------

Container 2

Regression

----------------

Container 3

API

----------------

Container 4

Payments

This reduces execution time.


Storing Reports

Map reports to the host machine.

Example

docker run \
-v ${PWD}/playwright-report:/app/playwright-report \
playwright-framework

Generated reports remain available after the container exits.


Running HTML Report

npx playwright show-report

Kubernetes Introduction

Docker manages containers.

Kubernetes manages many containers across one or more machines.


Why Kubernetes?

Benefits

  • Auto scaling
  • Self healing
  • Load balancing
  • High availability
  • Rolling updates
  • Automatic restarts

Kubernetes Architecture

               Kubernetes Cluster

                     │

        ┌────────────┼────────────┐

        ▼            ▼            ▼

      Node 1      Node 2      Node 3

        │            │            │

     Pod A        Pod B       Pod C

        │            │            │

  Playwright    Playwright   Playwright

Kubernetes Components

ComponentPurpose
ClusterEntire Kubernetes environment
NodePhysical or virtual machine
PodSmallest deployable unit
DeploymentManages Pods
ServiceNetwork access to Pods
ConfigMapStores configuration
SecretStores sensitive values
VolumePersistent storage

Pod

A Pod contains one or more containers.

Example

Pod

↓

Playwright Container

Deployment

A Deployment manages Pods.

Example

Deployment

↓

3 Pods

↓

Auto Restart

↓

Auto Scaling

Sample Deployment YAML

apiVersion: apps/v1

kind: Deployment

metadata:
  name: playwright

spec:
  replicas: 3

  selector:
    matchLabels:
      app: playwright

  template:

    metadata:

      labels:

        app: playwright

    spec:

      containers:

      - name: playwright

        image: playwright-framework:latest

Apply Deployment

kubectl apply -f deployment.yaml

Verify Pods

kubectl get pods

Verify Deployments

kubectl get deployments

Describe Pod

kubectl describe pod <pod-name>

View Logs

kubectl logs <pod-name>

This is the first step when diagnosing failures.


Execute Commands Inside a Pod

kubectl exec -it <pod-name> -- bash

Useful for troubleshooting.


ConfigMap

Store non-sensitive configuration.

Example

Base URL

Environment

Browser

Timeout

Avoid embedding these values in container images.


Secret Management

Never store:

  • Passwords
  • API Keys
  • Access Tokens

inside:

  • Dockerfile
  • Git Repository
  • Source Code

Use Kubernetes Secrets or an external secrets manager.


Volume

Containers are temporary.

Store reports using Persistent Volumes.

Playwright

↓

Report

↓

Persistent Volume

↓

Accessible Later

Auto Scaling

Example

2 Pods

↓

10 Pods

↓

20 Pods

↓

Back to 2

Kubernetes can scale based on resource usage or custom metrics.


CI/CD Flow

Developer

↓

Git Push

↓

GitHub Actions

↓

Build Docker Image

↓

Push Image

↓

Deploy Kubernetes

↓

Run Smoke Tests

↓

Run Regression

↓

Publish Report

↓

Notify Team

Enterprise Architecture

Developer

        │

        ▼

GitHub Repository

        │

        ▼

GitHub Actions

        │

        ▼

Docker Build

        │

        ▼

Container Registry

        │

        ▼

Kubernetes Cluster

        │

        ▼

Playwright Pods

        │

        ▼

Automation Execution

        │

        ▼

Reports

        │

        ▼

Slack / Email Notification

Docker Best Practices

  • Use the official Playwright Docker image.
  • Use npm ci instead of npm install in CI.
  • Keep images as small as possible.
  • Use .dockerignore.
  • Don’t store secrets in images.
  • Mount reports as volumes.
  • Pin image versions instead of relying on latest.
  • Run containers as a non-root user when possible.

Kubernetes Best Practices

  • Keep Pods stateless.
  • Use ConfigMaps for configuration.
  • Use Secrets for credentials.
  • Configure readiness and liveness probes when appropriate.
  • Set CPU and memory requests/limits.
  • Store reports outside Pods.
  • Scale horizontally instead of creating oversized Pods.
  • Use namespaces to separate environments such as Dev, QA, and UAT.

Docker vs Kubernetes

DockerKubernetes
Builds and runs containersOrchestrates containers
Runs on a single machineManages clusters
Manual scalingAutomatic scaling
Manual restartSelf-healing
Simple deploymentEnterprise deployment
Good for local developmentBest for production environments

Common Interview Questions

1. Why use Docker with Playwright?

Answer:
Docker provides a consistent execution environment across local machines and CI/CD systems, eliminating dependency and browser version differences.


2. Why use the official Playwright Docker image?

Answer:
It already contains compatible versions of Node.js, Playwright, browsers, and required Linux dependencies, reducing setup effort and compatibility issues.


3. What is the difference between a Docker image and a container?

Answer:
A Docker image is an immutable blueprint containing the application and its dependencies. A container is a running instance of that image.


4. Why use Kubernetes for Playwright?

Answer:
Kubernetes automates deployment, scaling, recovery, and management of Playwright containers, making it suitable for large-scale parallel test execution.


5. What is a Pod?

Answer:
A Pod is the smallest deployable unit in Kubernetes. It contains one or more containers that share networking and storage resources.


6. How do you securely manage credentials in Kubernetes?

Answer:
Use Kubernetes Secrets (or an enterprise secrets manager) and inject them into Pods as environment variables or mounted files instead of storing them in source code or Docker images.


7. How would you execute 5,000 Playwright tests quickly?

Answer:
Split tests into logical suites, run them in parallel across multiple Playwright workers and Kubernetes Pods, reuse authenticated sessions with storageState, use API-based test data setup where possible, and collect reports from shared storage.


8. What challenges have you seen when running Playwright in containers?

Answer:
Common challenges include browser resource consumption, report persistence, secure secret management, parallel test data collisions, and ensuring enough CPU and memory for stable execution. These are addressed through proper container configuration, persistent storage, isolated test data, and Kubernetes resource management.

SOLID Principles with Playwright Examples (TypeScript)

Introduction

SOLID is a set of five object-oriented design principles introduced by Robert C. Martin (Uncle Bob). These principles help developers build software that is:

  • Easy to maintain
  • Easy to extend
  • Highly reusable
  • Loosely coupled
  • Easy to test

In enterprise Playwright frameworks, following SOLID principles results in cleaner Page Objects, reusable utilities, scalable workflows, and better test automation architecture.


Why SOLID is Important in Automation Frameworks

Without SOLID principles, frameworks often become:

  • Huge Page Objects (1000+ lines)
  • Duplicate code
  • Difficult to maintain
  • Hard to extend
  • Strongly coupled
  • Fragile when the application changes

With SOLID principles:

  • Classes have clear responsibilities
  • New features require minimal changes
  • Code becomes reusable
  • Frameworks scale efficiently

What does SOLID stand for?

PrincipleFull Form
SSingle Responsibility Principle
OOpen Closed Principle
LLiskov Substitution Principle
IInterface Segregation Principle
DDependency Inversion Principle

S — Single Responsibility Principle (SRP)

Definition

A class should have only one reason to change.

Each class should have one responsibility.


Bad Example

A LoginPage doing everything.

class LoginPage {

    login() {}

    logout() {}

    readExcel() {}

    generateRandomUser() {}

    takeScreenshot() {}

    sendEmail() {}

}

Problems

  • Too many responsibilities
  • Difficult maintenance
  • Hard to reuse
  • Large file

Good Example

Separate responsibilities.

LoginPage

↓

Login only
ExcelUtility

↓

Read Excel
ScreenshotUtility

↓

Capture Screenshot
Logger

↓

Logging

Each class has a single purpose.


Enterprise Folder Structure

pages/

    LoginPage.ts

utils/

    ExcelUtility.ts

    Logger.ts

    ScreenshotUtility.ts

Benefits

✔ Small classes

✔ Easy maintenance

✔ Better readability

✔ Easier testing


Interview Question

Why is SRP important in Playwright?

Answer

It prevents Page Objects from becoming large and difficult to maintain. By separating responsibilities into page objects, utilities, workflows, and services, the framework becomes cleaner, reusable, and easier to extend.


O — Open Closed Principle (OCP)

Definition

Software entities should be open for extension but closed for modification.

Instead of changing existing code, extend it.


Bad Example

if(browser=="chromium"){

}

else if(browser=="firefox"){

}

else if(browser=="webkit"){

}

Whenever a new browser is added, this code must be modified.


Better Approach

Use the Strategy Pattern.

Browser Strategy

↓

Chromium

Firefox

WebKit

Adding a new browser:

Edge Strategy

No existing code changes.


Playwright Example

Instead of modifying

BrowserLauncher.ts

Create

ChromiumStrategy

FirefoxStrategy

WebKitStrategy

EdgeStrategy

Benefits

✔ Easy extension

✔ No modification

✔ Low risk

✔ Better architecture


Interview Question

Explain OCP using Playwright.

Answer

A browser launcher should support new browsers without changing existing code. Using Strategy Pattern, each browser has its own implementation, allowing new browsers to be added by creating new strategy classes rather than modifying the launcher.


L — Liskov Substitution Principle (LSP)

Definition

A derived class should be replaceable with its base class without breaking the application.


Example

Suppose

BasePage

contains

open()

waitForPage()

verifyTitle()

Now

LoginPage

DashboardPage

CheckoutPage

extend BasePage.

Any of them should work wherever a BasePage is expected.


Good Example

BasePage

↓

LoginPage

↓

DashboardPage

↓

CartPage

Each subclass honors the behavior defined by the base class.


Bad Example

Suppose BasePage defines

openPage()

but CheckoutPage throws an exception because it cannot open itself.

That violates LSP.


Benefits

✔ Better inheritance

✔ Predictable behavior

✔ Easier maintenance


Interview Question

How does LSP help automation?

Answer

It ensures that all page objects derived from a common base behave consistently. Shared framework code can work with any page object without requiring special handling.


I — Interface Segregation Principle (ISP)

Definition

Clients should not be forced to depend on interfaces they don’t use.


Bad Example

interface Utility{

readExcel();

takeScreenshot();

sendEmail();

uploadFile();

downloadFile();

}

Every implementing class must define unnecessary methods.


Better Example

Separate interfaces.

ExcelReader

↓

readExcel()

ScreenshotService

↓

takeScreenshot()

FileUploader

↓

upload()

Benefits

✔ Small interfaces

✔ Easier implementation

✔ Better readability


Interview Question

Why is ISP useful in Playwright?

Answer

It avoids creating large interfaces that force unrelated implementations. Separate interfaces for reporting, screenshots, file handling, and data readers keep the framework modular and easier to maintain.


D — Dependency Inversion Principle (DIP)

Definition

High-level modules should not depend on low-level modules.

Both should depend on abstractions.


Bad Example

class LoginWorkflow{

private login=new LoginPage(page);

}

Strong coupling.


Better Example

Inject dependency.

constructor(private loginPage: LoginPage){

}

Now

LoginWorkflow

doesn’t create LoginPage.

Someone else provides it.


Architecture

Test

↓

Fixture

↓

Workflow

↓

Page Object

Each layer receives dependencies instead of creating them.


Playwright Example

Playwright Fixtures naturally support Dependency Injection.

Instead of

const page=new LoginPage(browserPage);

Fixtures inject

loginPage

directly into the test.


Benefits

✔ Loose coupling

✔ Easy testing

✔ Easy mocking

✔ Better scalability


Interview Question

How does Playwright support Dependency Injection?

Answer

Playwright fixtures inject dependencies such as browser instances, pages, authenticated sessions, and page objects into test functions. This reduces object creation inside tests and promotes loose coupling.


SOLID Applied to a Playwright Framework

                     Test Layer
                         │
                         ▼
                  Workflow Layer
                         │
                         ▼
                 Page Object Layer
                         │
                         ▼
                 Utility/Service Layer
                         │
                         ▼
                Playwright Framework
                         │
                         ▼
                     Browser

Each layer follows a specific responsibility.


Before SOLID

LoginPage

1000 Lines

↓

Login

Logout

Excel

JSON

API

Logger

Screenshot

Database

Email

Random Data

Very difficult to maintain.


After SOLID

LoginPage

↓

Login Only
Logger

↓

Logging Only
API Client

↓

API Only
Excel Utility

↓

Excel Only
Workflow

↓

Business Process

Clean architecture.


SOLID + Design Patterns

SOLID PrincipleDesign Pattern
SRPPage Object Model
OCPStrategy Pattern
LSPBase Page Inheritance
ISPSmall Interfaces
DIPFixtures + Dependency Injection

Real Enterprise Example

Suppose you’re automating an e-commerce application.

Without SOLID

CheckoutPage

↓

Login

↓

Search

↓

Add Product

↓

Payment

↓

Database

↓

Email

↓

Report

↓

Screenshot

One class controls everything.


With SOLID

CheckoutWorkflow

↓

LoginPage

↓

ProductPage

↓

CartPage

↓

PaymentPage

↓

OrderPage

Utilities

Logger

API

Screenshot

Excel

JSON

Random Data

Every class has one responsibility.


Enterprise Benefits

Following SOLID results in:

  • Smaller Page Objects
  • Better readability
  • Easier debugging
  • Cleaner architecture
  • Reusable code
  • Easier onboarding for new team members
  • Better unit and integration testing
  • Faster feature development
  • Simpler code reviews

Common SOLID Mistakes in Playwright

❌ Putting business workflows inside page objects

Move business flows to a separate Workflow (Facade) layer.


❌ Creating one huge Utility class

Split it into focused utilities:

  • Logger
  • DateUtility
  • JsonUtility
  • ExcelUtility
  • ScreenshotUtility

❌ Hardcoding object creation

Avoid:

const loginPage = new LoginPage(page);

Prefer dependency injection through Playwright fixtures or a factory.


❌ Large BasePage classes

Keep BasePage limited to truly common functionality such as navigation, waiting, or shared helpers. Don’t force unrelated pages to inherit unnecessary behavior.


❌ Large interfaces

Create small, purpose-specific interfaces rather than one interface containing dozens of unrelated methods.


Senior Playwright Interview Questions

1. What are SOLID principles?

Answer

SOLID is a set of five object-oriented design principles that improve maintainability, extensibility, reusability, and scalability. They help reduce coupling and encourage clean architecture.


2. Which SOLID principle is most important in automation?

Answer

All five are valuable, but Single Responsibility Principle (SRP) is often the most impactful because it prevents oversized Page Objects and encourages separation of concerns across pages, workflows, utilities, and services.


3. How does Playwright support Dependency Inversion?

Answer

Playwright fixtures provide dependency injection by supplying browser instances, contexts, pages, and custom page objects to tests. Tests depend on abstractions provided by fixtures instead of creating concrete objects themselves.


4. How do SOLID principles improve a Playwright framework?

Answer

They make the framework modular, reusable, easier to extend, and simpler to maintain. New functionality can often be added by introducing new classes rather than modifying existing ones, reducing the risk of breaking stable automation.


5. How do SOLID principles relate to design patterns?

Answer

Design patterns are practical implementations of SOLID ideas. For example:

  • SRP → Page Object Model, Workflow layer
  • OCP → Strategy Pattern
  • LSP → Base Page inheritance
  • ISP → Focused interfaces
  • DIP → Fixtures and Dependency Injection

Using SOLID together with patterns like Factory, Builder, Strategy, and Facade results in an enterprise-grade Playwright framework that is easier to scale and maintain over time.

Advanced Playwright Design Patterns (Factory, Builder, Strategy)

As Playwright automation frameworks grow, applying design patterns helps improve maintainability, scalability, reusability, and test readability. These patterns are commonly discussed in interviews for Senior SDET, Automation Architect, and QA Lead roles.

Why Use Design Patterns?

Without design patterns, automation frameworks often suffer from:

  • Duplicate code
  • Tight coupling
  • Difficult maintenance
  • Large Page Objects
  • Hardcoded logic
  • Poor scalability

Using design patterns helps create frameworks that are easier to extend and maintain.


1. Factory Pattern

What is Factory Pattern?

The Factory Pattern centralizes object creation instead of allowing tests to instantiate classes directly.

Instead of:

const loginPage = new LoginPage(page);
const dashboardPage = new DashboardPage(page);
const cartPage = new CartPage(page);

Create objects through a factory.


Why Use Factory Pattern?

Benefits

  • Centralized object creation
  • Easier maintenance
  • Supports Dependency Injection
  • Simplifies test code
  • Reduces duplicate initialization

Architecture

               Test

                │

                ▼

          Page Factory

        ┌──────┼──────┐

        ▼      ▼      ▼

   LoginPage Dashboard CartPage

Example Implementation

LoginPage

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

export class LoginPage {

    constructor(private readonly page: Page) {}

    async login(username: string, password: string) {
        // Login steps
    }

}

DashboardPage

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

export class DashboardPage {

    constructor(private readonly page: Page) {}

    async verifyDashboard() {
        // Verification logic
    }

}

PageFactory

import { Page } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';

export class PageFactory {

    constructor(private readonly page: Page) {}

    get loginPage() {
        return new LoginPage(this.page);
    }

    get dashboardPage() {
        return new DashboardPage(this.page);
    }

}

Usage

const factory = new PageFactory(page);

await factory.loginPage.login(username, password);

await factory.dashboardPage.verifyDashboard();

Advantages

✔ Cleaner tests

✔ Centralized object creation

✔ Easy maintenance

✔ Supports Dependency Injection

✔ Easily extendable


Disadvantages

  • Slightly more abstraction
  • May be unnecessary for very small projects

Factory Pattern Interview Questions

Q1. Why use the Factory Pattern?

Answer

To centralize object creation, reduce duplicate initialization, improve maintainability, and simplify test code.


Q2. When should you avoid the Factory Pattern?

Answer

For very small frameworks with only a few page objects, a factory may add unnecessary complexity.


2. Builder Pattern

What is Builder Pattern?

The Builder Pattern creates complex objects step by step.

Instead of creating long constructors:

const user = {

    username: "admin",

    password: "pass",

    role: "Manager",

    department: "QA",

    active: true

};

Use a builder.


Why Use Builder Pattern?

Benefits

  • Readable object creation
  • Optional fields
  • Fluent API
  • Easier maintenance
  • Less constructor complexity

Example Architecture

             User Builder

                    │

                    ▼

             Build User Object

                    │

                    ▼

              Test Uses Object

Example

User Model

export interface User {

    username: string;

    password: string;

    role: string;

    department: string;

}

Builder

export class UserBuilder {

    private user = {

        username: "",

        password: "",

        role: "",

        department: ""

    };

    withUsername(username: string) {

        this.user.username = username;

        return this;

    }

    withPassword(password: string) {

        this.user.password = password;

        return this;

    }

    withRole(role: string) {

        this.user.role = role;

        return this;

    }

    withDepartment(department: string) {

        this.user.department = department;

        return this;

    }

    build() {

        return this.user;

    }

}

Usage

const user = new UserBuilder()

    .withUsername("admin")

    .withPassword("admin123")

    .withRole("Manager")

    .withDepartment("QA")

    .build();

Benefits

  • Fluent syntax
  • Easy to read
  • Easy to extend
  • Avoids large constructors
  • Great for test data creation

Real Playwright Use Cases

Builder Pattern is useful for creating:

  • Test users
  • Customer objects
  • Orders
  • Payment requests
  • API payloads
  • Product data
  • Registration forms

Builder Pattern Interview Questions

Q1. Why use Builder instead of constructors?

Answer

Builders improve readability, support optional fields, and prevent constructors with many parameters that are difficult to understand and maintain.


Q2. Where is Builder commonly used in automation?

Answer

Creating complex test data, API payloads, registration forms, and domain objects used across tests.


3. Strategy Pattern

What is Strategy Pattern?

The Strategy Pattern allows you to switch algorithms or behaviors at runtime without changing the client code.

Instead of writing:

if(browser === "chrome") {

}

else if(browser === "firefox") {

}

else if(browser === "webkit") {

}

Move each behavior into its own strategy.


Why Use Strategy Pattern?

Benefits

  • Easy to add new behavior
  • No large if-else chains
  • Better maintainability
  • Open for extension
  • Easier unit testing

Architecture

             Test

              │

              ▼

      Browser Strategy

       ┌─────┼─────┐

       ▼     ▼     ▼

 Chrome Firefox WebKit

Example

Interface

export interface BrowserStrategy {

    launch(): Promise<void>;

}

Chrome Strategy

export class ChromiumStrategy implements BrowserStrategy {

    async launch() {

        console.log("Launch Chromium");

    }

}

Firefox Strategy

export class FirefoxStrategy implements BrowserStrategy {

    async launch() {

        console.log("Launch Firefox");

    }

}

Context

export class BrowserLauncher {

    constructor(private strategy: BrowserStrategy) {}

    async start() {

        await this.strategy.launch();

    }

}

Usage

const launcher = new BrowserLauncher(

    new ChromiumStrategy()

);

await launcher.start();

Playwright Use Cases

Strategy Pattern is useful for:

  • Browser selection
  • Authentication methods
  • Environment-specific behavior
  • Payment gateway flows
  • Report generation
  • File upload mechanisms
  • Different login types (UI, API, SSO)

Authentication Example

               Login

                 │

        ┌────────┼────────┐

        ▼        ▼        ▼

     UI Login  API Login  SSO Login

The test selects the appropriate authentication strategy without changing its own logic.


Report Generation Example

Report Strategy

      │

┌─────┼──────┐

▼     ▼      ▼

HTML Allure JUnit

Advantages of Strategy Pattern

✔ Removes large conditional statements

✔ Supports Open/Closed Principle

✔ Easy to extend

✔ Highly testable

✔ Easy maintenance


Factory vs Builder vs Strategy

FeatureFactoryBuilderStrategy
Main PurposeCreate objectsConstruct complex objectsChange behavior dynamically
FocusObject creationObject configurationAlgorithm/behavior selection
ReturnsReady-to-use objectsConfigured objectSelected implementation
Typical Playwright UsePage Objects, API clientsTest data, payloadsBrowser, authentication, reporting
Supports ExtensibilityYesYesExcellent

Combining Patterns in a Playwright Framework

Enterprise frameworks often combine multiple patterns:

                   Test
                     │
                     ▼
               Workflow Layer
                     │
                     ▼
               Page Factory
                     │
        ┌────────────┼────────────┐
        ▼            ▼            ▼
   LoginPage   ProductPage   CartPage
                     │
                     ▼
              Builder Objects
                     │
                     ▼
             Test Data Objects
                     │
                     ▼
             Strategy Selection
        ┌────────────┼────────────┐
        ▼            ▼            ▼
    Browser      Authentication   Reporting

Real-World Example

Imagine an e-commerce application:

  1. Factory Pattern
    • Creates LoginPage, CartPage, CheckoutPage, and OrderPage.
  2. Builder Pattern
    • Creates customer profiles, shipping addresses, payment requests, and order payloads.
  3. Strategy Pattern
    • Chooses:
      • Browser (Chromium, Firefox, WebKit)
      • Login method (UI, API, SSO)
      • Payment method (Credit Card, PayPal, UPI)
      • Report type (HTML, Allure)

Together, these patterns produce a framework that is easier to maintain, easier to extend, and capable of supporting large-scale automation projects with minimal changes to existing code.


Senior SDET Interview Questions

1. Which design patterns have you used in your Playwright framework?

Answer:
I commonly use the Page Object Model, Factory Pattern for page object creation, Builder Pattern for test data and API payloads, Strategy Pattern for browser and authentication selection, Facade/Workflow Pattern for business processes, and Dependency Injection through Playwright fixtures.


2. Which design pattern is most useful in automation frameworks?

Answer:
There isn’t a single best pattern. Page Object Model is foundational, Factory simplifies object creation, Builder improves test data management, Strategy removes conditional logic, and Workflow (Facade) keeps business flows reusable. The best choice depends on the problem being solved.


3. How do these patterns improve maintainability?

Answer:
They separate responsibilities, reduce code duplication, isolate changes, improve readability, and make it easier to extend the framework without modifying existing test code. This aligns with SOLID principles and results in a more scalable automation architecture.

Real-World Enterprise Playwright Framework Implementation

In this section, we’ll build a production-ready Playwright automation framework and explain how each folder contributes to the overall architecture. This is the type of framework commonly used in enterprise applications and discussed in senior SDET interviews.

Step 1: Enterprise Framework Folder Structure

Playwright-Framework
│
├── .github/
│     └── workflows/
│            playwright.yml
│
├── pages/
│     LoginPage.ts
│     DashboardPage.ts
│     ProductPage.ts
│     CartPage.ts
│     CheckoutPage.ts
│
├── workflows/
│     LoginWorkflow.ts
│     CheckoutWorkflow.ts
│
├── fixtures/
│     baseFixture.ts
│     loginFixture.ts
│
├── utils/
│     Logger.ts
│     WaitUtility.ts
│     JsonUtility.ts
│     RandomData.ts
│     ScreenshotUtility.ts
│     Environment.ts
│
├── api/
│     CustomerAPI.ts
│     OrderAPI.ts
│
├── test-data/
│     login.json
│     checkout.json
│     products.json
│
├── constants/
│     URL.ts
│     Messages.ts
│     Timeout.ts
│
├── tests/
│     login.spec.ts
│     checkout.spec.ts
│     orders.spec.ts
│
├── reports/
├── screenshots/
├── traces/
├── videos/
│
├── playwright.config.ts
├── global-setup.ts
├── global-teardown.ts
├── package.json
└── README.md

Step 2: Responsibilities of Each Folder

pages/

Contains only page-related logic.

Example:

LoginPage

DashboardPage

CheckoutPage

PaymentPage

A page object should:

  • Store page locators
  • Expose page actions
  • Optionally expose page-specific validations

A page object should not:

  • Read Excel files
  • Generate random data
  • Call unrelated APIs
  • Contain business workflows spanning multiple pages

workflows/

Many beginners place all business logic inside page objects.

Instead, create workflow classes.

Example:

Login Workflow

↓

Open Login Page

↓

Enter Username

↓

Enter Password

↓

Click Login

↓

Verify Dashboard

This keeps page objects small and reusable.

Examples:

LoginWorkflow

CheckoutWorkflow

OrderWorkflow

PaymentWorkflow

fixtures/

Provides reusable setup.

Examples:

Browser Fixture

Login Fixture

API Fixture

Database Fixture

Avoid creating browser instances manually in every test.


utils/

Contains reusable helper classes.

Examples:

Date Utility

Random Data

Logger

Encryption

Screenshot Utility

Download Utility

Upload Utility

PDF Utility

Utilities should remain generic and independent of specific pages.


api/

Store API helper classes separately from UI automation.

Example:

Customer API

Order API

Product API

Payment API

This separation allows UI and API automation to evolve independently.


constants/

Avoid hardcoding values.

Examples:

Application URL

Messages

Timeout

Roles

Endpoints

test-data/

Contains:

JSON

CSV

Excel

YAML

XML

Avoid storing test data inside test files.


Step 3: Workflow Layer

Enterprise frameworks often include a workflow (or business layer).

Architecture:

Test

↓

Workflow

↓

Page Objects

↓

Playwright

Example

Instead of

Test

↓

Login Page

↓

Dashboard Page

↓

Cart Page

↓

Checkout Page

Use

Test

↓

Checkout Workflow

↓

All Page Objects

Benefits:

  • Cleaner tests
  • Less duplication
  • Easier maintenance

Step 4: Layered Architecture

                    Tests
                      │
                      ▼
              Business Workflows
                      │
                      ▼
                Page Objects
                      │
                      ▼
                  Utilities
                      │
                      ▼
              Playwright API
                      │
                      ▼
                  Browser
                      │
                      ▼
               Web Application

Each layer has a single responsibility.


Step 5: Login Workflow

Instead of repeating login steps in multiple tests:

Open Login

↓

Enter Username

↓

Enter Password

↓

Click Login

↓

Verify Dashboard

Create a reusable login workflow.

Advantages:

  • Centralized login logic
  • Easy updates
  • Cleaner test cases

Step 6: Checkout Workflow

A checkout workflow might include:

Login

↓

Search Product

↓

Open Product

↓

Add to Cart

↓

Checkout

↓

Payment

↓

Order Confirmation

This represents one business process composed of multiple page objects.


Step 7: API + UI Integration

Many enterprise projects create data through APIs before validating it in the UI.

Example:

API

Create Customer

↓

API

Create Product

↓

API

Generate Order

↓

UI

Search Order

↓

Verify Status

Advantages:

  • Faster setup
  • Less UI dependency
  • More stable tests

Step 8: Data Cleanup Strategy

Automation should avoid leaving unnecessary test data behind.

Typical cleanup:

Create User

↓

Execute Test

↓

Delete User

Or

Create Order

↓

Execute Test

↓

Cancel/Delete Order

Cleanup can be handled:

  • After each test
  • After all tests
  • Through scheduled database jobs (depending on the environment)

Step 9: Reusable Components

Good frameworks maximize reuse.

Reusable examples:

Login

Logout

Navigation

Calendar Selection

Dropdown Selection

Table Reader

File Upload

File Download

Avoid copying the same logic into multiple page objects.


Step 10: Configuration Strategy

Separate configurations by environment.

Example:

Development

↓

QA

↓

UAT

↓

Production

Each environment should define:

  • Base URL
  • API URL
  • Credentials (prefer secure secrets management)
  • Timeouts
  • Feature flags (if applicable)

Step 11: Secure Credential Management

Never commit credentials to source control.

Use:

  • Environment variables
  • Secret managers
  • CI/CD secrets
  • Vault solutions (for enterprise environments)

Avoid:

admin

password123

inside source files.


Step 11: Logging Architecture

Recommended logging flow:

Test Starts

↓

Log Browser Launch

↓

Log Navigation

↓

Log User Actions

↓

Log Validations

↓

Log Result

↓

Log Browser Close

Keep logs informative but concise.


Step 12: Screenshot Strategy

Capture screenshots:

  • On failure
  • Before destructive actions (optional)
  • During debugging (optional)

Avoid capturing screenshots after every step in normal execution.


Step 13: Trace Strategy

Capture traces when:

  • A test fails
  • Debugging intermittent failures
  • Investigating complex synchronization issues

Trace Viewer provides:

  • Timeline
  • DOM snapshots
  • Network activity
  • Console messages
  • User actions

Step 14: Parallel Testing Strategy

Example:

Worker 1

Authentication

--------------------

Worker 2

Orders

--------------------

Worker 3

Payments

--------------------

Worker 4

Reports

Ensure:

  • No shared state
  • Independent test data
  • Separate browser contexts

Step 15: Cross-Browser Strategy

Recommended execution:

Chromium

↓

Firefox

↓

WebKit

Run smoke tests on all browsers and reserve full regression for the browsers required by your project.


Step 16: Continuous Integration Flow

Developer Commit

↓

Pull Request

↓

Code Review

↓

Build

↓

Install Dependencies

↓

Install Browsers

↓

Run Lint

↓

Run Unit Tests (if applicable)

↓

Run Smoke Automation

↓

Run Regression

↓

Generate Report

↓

Publish Artifacts

↓

Notify Team

Step 17: Pull Request Checklist

Before creating a PR:

  • Code builds successfully
  • All tests pass
  • ESLint passes
  • Prettier formatting applied
  • No hardcoded values
  • No static waits
  • No duplicate methods
  • Meaningful commit messages
  • Updated documentation (if required)

Step 18: Versioning Strategy

Example:

Version 1.0

↓

Version 1.1

↓

Version 1.2

↓

Version 2.0

Tag framework releases to simplify rollback and traceability.


Step19: Recommended NPM Scripts

Typical scripts include:

test

test:smoke

test:regression

test:headed

test:chrome

test:firefox

test:webkit

report

lint

format

These provide a consistent way to execute different suites.


Step 20: Enterprise Coding Guidelines

Follow these principles:

Single Responsibility Principle

Each class should have one responsibility.


Open/Closed Principle

Design classes so they can be extended without modifying existing behavior wherever practical.


DRY (Don’t Repeat Yourself)

Move repeated logic into reusable methods or workflows.


KISS (Keep It Simple, Stupid)

Prefer simple, readable solutions over unnecessary complexity.


YAGNI (You Aren’t Gonna Need It)

Don’t implement features before they are actually required.


Step 21: Enterprise Framework Maturity Model

Beginner Framework

  • Tests
  • Page Objects

Intermediate Framework

  • Fixtures
  • Utilities
  • Reporting
  • Environment support

Advanced Framework

  • Business workflows
  • API integration
  • Parallel execution
  • CI/CD
  • Authentication reuse
  • Robust logging
  • Cross-browser support

Enterprise Framework

  • Scalable architecture
  • Secure configuration management
  • API + UI hybrid testing
  • Containerized execution
  • Cloud execution
  • Comprehensive reporting
  • Test analytics
  • Quality gates in CI/CD

Senior Playwright Interview Questions & Answers

1. Why would you introduce a Workflow Layer?

Answer:

A workflow layer encapsulates complete business processes that span multiple pages. This keeps page objects focused on page interactions while making test cases shorter, more readable, and easier to maintain.


2. What is the difference between a Page Object and a Workflow?

Answer:

A Page Object models a single page by exposing its locators and actions.

A Workflow coordinates multiple page objects to accomplish a business process such as placing an order or completing user registration.


3. Why shouldn’t business logic be placed inside page objects?

Answer:

Business workflows often involve multiple pages. Keeping them in page objects creates oversized classes, increases coupling, and makes reuse difficult. Separating workflows improves maintainability and follows the Single Responsibility Principle.


4. How do you organize a framework for thousands of test cases?

Answer:

I organize it into layers (tests, workflows, page objects, utilities, API helpers, fixtures), group tests by feature, externalize configuration and test data, enable parallel execution, use authentication reuse, and integrate reporting and CI/CD.


5. How do you reduce execution time in Playwright?

Answer:

  • Execute tests in parallel.
  • Reuse authenticated sessions with storageState.
  • Create test data through APIs instead of the UI where appropriate.
  • Categorize suites into smoke and regression.
  • Avoid unnecessary browser launches and redundant setup.
  • Remove fixed waits.

6. How do you handle flaky tests?

Answer:

I investigate the root cause rather than relying on retries. Common improvements include using stable locators, waiting on application state instead of time, isolating test data, improving cleanup, and reviewing application synchronization issues.


7. What qualities define a production-ready Playwright framework?

Answer:

A production-ready framework has a clean layered architecture, reusable fixtures, focused page objects, workflow abstraction, secure configuration management, reliable reporting, logging, authentication reuse, CI/CD integration, parallel execution, cross-browser support, coding standards, and comprehensive