Skip to content
SQA Tools

SQA Tools

Online Learning Platform

  • Python
    • Python Tutorial
    • All Practice Exercises
    • Basic Programs
    • If-Else Programs
    • Loop Programs
    • String Programs
    • List Programs
    • Tuple Programs
    • Dictionary Programs
    • Set Programs
    • Function Programs
    • File Handling Programs
    • OS Module Programs
    • OOPS Programs
    • NumPy Programs
    • Pandas Programs
  • Automation
    • Selenium with Python
    • Playwright with TypeScript
    • Playwright with Python
    • Playwright Training Notes
    • Playwright Mock Interview
    • Playwright Python Mock Interview
    • Playwright Practice
    • Automation Practice Page
    • Dummy Booking Website
    • Dummy E-commerce Website
    • Dummy Banking Website
    • API Testing Practice
  • SQL
    • SQL Tutorial (69 lessons)
    • SQL Joins
    • Window Functions
    • Project: E-commerce Sales Analysis
    • Project: Library Management
    • Project: HR & Payroll Analytics
    • Project: Banking Transactions
    • Project: QA Test Results Tracker
    • SQL Interview Questions
  • Git & GitHub
    • Git & GitHub Tutorial
    • GitHub Actions Tutorial
    • Git Cheat Sheet
    • Git & GitHub Interview Questions
  • ISTQB
    • ISTQB Foundation (CTFL) Guide
    • Preparation Guide & Study Plan
    • Chapter-wise Study Notes
    • Sample Papers with Answers
  • Quizzes
    • Python MCQ
    • Playwright Mock Interview
    • Playwright Python Mock Interview
  • Blog
  • Jobs

Playwright Testing Practice

by
Playwright Tutorial›Module 14: Real-World Projects and Assignments
Lesson 84 of 9192% through the course
Playwright Practice Page | Automation Testing Playground

Playwright Practice & Automation Testing

Free hands-on browser automation practice for Playwright, Selenium, and Cypress

Data Entry Form Upload Files Tables Tabs Buttons & Alerts Mouse Actions Widgets Shadow DOM

Interactive UI Automation Practice Page

Build end-to-end testing skills with realistic browser automation exercises. Practice Playwright locators and actions on forms, dropdowns, checkboxes, tables, file uploads, alerts, tabs, iframes, drag-and-drop, and Shadow DOM controls. This page also works for Selenium and Cypress practice.

Automation Testing Practice Form

Gender
Days of the week

Upload Files

Web Tables for Playwright Locator Practice

Static Web Table Testing

NameRoleTeamLocation
AveryQA EngineerWebSeattle
JordanDeveloperMobileToronto
RileyAutomation EngineerWebLondon
MorganProduct AnalystDataBerlin

Dynamic Web Table Testing

BrowserVersionPlatformStatus
ChromiumLatestWindowsReady
FirefoxLatestmacOSReady
WebKitLatestLinuxIn progress

Search, Sort, and Paginate Table Records

Department

Tabs

This is the content for the first tab.
This is the content for the second tab.
This is the content for the third tab.

Dynamic Button

Alerts & Popups

Mouse Actions

Mouse Hover

Mobiles Laptops

Mobiles menu item target

Laptops menu item target

Double Click

Drag and Drop

Drag this item
Drop it here

Widgets & Links

Slider

Search and additional form fields

Choose practice options

Scrolling dropdown

Labels and links

Go to the data entry form · Go to the tables

Use the links above to practice navigation and anchor locators.

Hidden elements & AJAX-style update

This element was hidden until the button was clicked.

Embedded frame

Shadow DOM

Practice locating and interacting with controls inside an open shadow root.

Local automation practice page · No external assets or services required
← Previous · Lesson 83Module-wise Practice AssignmentsNext → · Lesson 85Playwright Interview Questions and Answers
View the full Playwright tutorial syllabus
Categories Uncategorized 1 Comment

Senior QA Automation Engineer: Manager Round Questions & Answer

by
Senior QA Automation Engineer Manager Round: 90+ Questions and Answers
Interview preparation guide

Senior QA Automation Engineer: Manager Round Questions & Answers

Prepare for senior QA and SDET interviews with practical sample answers covering quality strategy, team leadership, Playwright, TypeScript, API automation, CI/CD, release decisions, and AI-assisted testing.

90+ interview questionsSample answersInteractive searchSenior SDET / QA
How to use this guide: Try answering each question out loud before revealing the sample answer. Adapt examples to your real projects and use genuine metrics only. These are interview practice answers, not scripts to memorize word for word.
Loading questions…0 answers practiced

Senior QA interview readiness checklist

Use this checklist before the manager round.

Interview tip: At senior level, explain why you chose an approach, the trade-offs you considered, how you handled risks, and what outcome you achieved. Do not invent project details or metrics. Replace generic examples with your own experience.
Categories Uncategorized 1 Comment

150+ SQL & MySQL Scenario-Based Interview Questions with Answers

by
150+ SQL & MySQL Scenario-Based Interview Questions
Interview preparation guide

150+ SQL & MySQL Scenario-Based Interview Questions

Learn to solve real database problems—not just memorize syntax. Practise joins, duplicates, subqueries, window functions, transactions, query tuning, and SQL validation scenarios asked in QA, SDET, backend, and data-focused interviews.

150+Interview scenarios
7 daysSuggested study plan
Hands-onQueries + mini quiz

Best for: QA Engineers, SDETs, Automation Engineers, Backend Developers, Data Analysts, and Automation Leads.

Jump to a topic

  • Practice database schema
  • SQL fundamentals
  • JOIN scenarios
  • Duplicates & data quality
  • Advanced SQL & window functions
  • MySQL internals & performance
  • QA / SDET database testing
  • Transactions & schema design
  • Interactive practice tracker
  • Knowledge-check quiz
  • 7-day study plan

1. Start with a practice schema

Use these sample tables to practise the questions below. Examples use MySQL 8.0+ syntax.

CREATE TABLE departments (
  department_id INT PRIMARY KEY,
  department_name VARCHAR(100)
);

CREATE TABLE employees (
  employee_id INT PRIMARY KEY,
  employee_name VARCHAR(100),
  department_id INT,
  salary DECIMAL(10,2),
  manager_id INT,
  hire_date DATE,
  FOREIGN KEY (department_id)
    REFERENCES departments(department_id)
);

CREATE TABLE customers (
  customer_id INT PRIMARY KEY,
  customer_name VARCHAR(100),
  email VARCHAR(150),
  city VARCHAR(100)
);

CREATE TABLE orders (
  order_id INT PRIMARY KEY,
  customer_id INT,
  order_date DATETIME,
  amount DECIMAL(10,2),
  status VARCHAR(30),
  FOREIGN KEY (customer_id)
    REFERENCES customers(customer_id)
);

CREATE TABLE payments (
  payment_id INT PRIMARY KEY,
  order_id INT,
  payment_amount DECIMAL(10,2),
  payment_status VARCHAR(30),
  payment_date DATETIME
);
Practice tip: Before writing a query, clarify the business rule. For example, “duplicate payment” could mean two successful payments, repeated payment attempts, or multiple valid partial payments.

2. SQL fundamentals: questions 1–20

#ScenarioConcept
1Find employees earning more than ₹50,000.WHERE
2Find salaries between ₹40,000 and ₹80,000.BETWEEN
3Find employees whose names start with A.LIKE
4Find employees without a department.NULL / LEFT JOIN
5Find employees whose salary is missing.IS NULL
6List unique customer cities.DISTINCT
7Count all employees.COUNT
8Calculate average salary.AVG
9Find minimum and maximum salary.MIN / MAX
10Calculate total salary expense.SUM
11Count employees per department.GROUP BY
12Find departments with more than five employees.HAVING
13Find customers from Indore or Bhopal.IN
14Find employees hired during 2025.Date ranges
15Sort employees by salary descending.ORDER BY
16Return the five highest-paid employees.LIMIT
17Display missing salary as zero.COALESCE
18Display employee names in uppercase.UPPER
19Extract the year from a hire date.YEAR
20Find employees earning above company average.Subquery

Example: Employees earning above the company average

SELECT employee_name, salary
FROM employees
WHERE salary > (
  SELECT AVG(salary) FROM employees
);

Example: Find employees hired during 2025

SELECT employee_id, employee_name, hire_date
FROM employees
WHERE hire_date >= '2025-01-01'
  AND hire_date <  '2026-01-01';

Using a range can allow better use of an index on the date column than wrapping the column in YEAR().

3. JOIN scenarios: questions 21–35

#ScenarioApproach
21Display every order with its customer name.INNER JOIN
22Find customers who never ordered.LEFT JOIN + IS NULL
23Find orders whose customer is missing.LEFT JOIN from orders
24Show all customers, including those without orders.LEFT JOIN
25Display employee names and department names.JOIN
26Find employees without a valid department.LEFT JOIN
27Display each employee and their manager.Self join
28Find customers with more than three orders.JOIN + GROUP BY
29Display orders with payment details.Multi-table join
30Find orders with no payment record.NOT EXISTS
31Find customers with successful payments.JOIN + filter
32Show departments with zero employees too.LEFT JOIN
33Find employees in the same department as a given employee.Self join
34Find customers who ordered and paid.Multi-table joins
35Find customers with orders but no completed payment.NOT EXISTS

Example: Customers who never placed an order

SELECT c.customer_id, c.customer_name
FROM customers c
LEFT JOIN orders o
  ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;

Example: Employee and manager names

SELECT e.employee_name AS employee,
       m.employee_name AS manager
FROM employees e
LEFT JOIN employees m
  ON e.manager_id = m.employee_id;

Example: Orders with no payment record

SELECT o.order_id
FROM orders o
WHERE NOT EXISTS (
  SELECT 1
  FROM payments p
  WHERE p.order_id = o.order_id
);
Common trap: A filter on the right-side table in a LEFT JOIN’s WHERE clause can remove unmatched rows. Decide whether the filter belongs in ON or WHERE based on whether unmatched left-side rows must remain.

4. Duplicate records & data-quality scenarios: questions 36–55

Example: Find duplicate email addresses

SELECT email, COUNT(*) AS occurrences
FROM customers
WHERE email IS NOT NULL
GROUP BY email
HAVING COUNT(*) > 1;

Example: Return all customer rows with duplicate emails

SELECT c.*
FROM customers c
JOIN (
  SELECT email
  FROM customers
  WHERE email IS NOT NULL
  GROUP BY email
  HAVING COUNT(*) > 1
) d ON d.email = c.email;

Example: Find missing or blank emails

SELECT *
FROM customers
WHERE email IS NULL OR TRIM(email) = '';

Example: Find negative order amounts

SELECT *
FROM orders
WHERE amount < 0;

Example: Find payment amounts that differ from order amounts

This example assumes one successful payment should equal the order amount. If split or partial payments are allowed, aggregate successful payments first.

SELECT o.order_id,
       o.amount AS order_amount,
       p.payment_amount
FROM orders o
JOIN payments p ON p.order_id = o.order_id
WHERE p.payment_status = 'SUCCESS'
  AND p.payment_amount <> o.amount;

More practice questions

  1. Find duplicate rows based on customer name and city.
  2. Find duplicate orders for a customer on the same date—after confirming the business rule.
  3. Find orphan payment records.
  4. Find names with leading or trailing spaces.
  5. Find duplicate emails after normalizing case and whitespace.
  6. Find missing IDs in a sequence.
  7. Find orders with future dates.
  8. Identify malformed email addresses.
  9. Find records where created date is later than updated date.
  10. Validate mandatory columns across several tables.
  11. Compare source and target row counts.
  12. Identify repeated transactions caused by retries.
  13. Find values outside an approved status list.
  14. Detect inconsistent whitespace or capitalization in category fields.
  15. Find duplicates without a unique identifier.
  16. Design a safe deduplication process that retains the correct record.
  17. Find rows that violate a business-specific data rule.
  18. Identify unexpected NULL values in required fields.

5. Advanced SQL & window functions: questions 56–75

Second-highest distinct salary

SELECT MAX(salary) AS second_highest_salary
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

Top three distinct salary levels in each department

WITH ranked AS (
  SELECT employee_id, employee_name, department_id, salary,
         DENSE_RANK() OVER (
           PARTITION BY department_id
           ORDER BY salary DESC
         ) AS salary_rank
  FROM employees
  WHERE salary IS NOT NULL
)
SELECT *
FROM ranked
WHERE salary_rank <= 3;

Latest order for each customer

WITH ranked AS (
  SELECT o.*,
         ROW_NUMBER() OVER (
           PARTITION BY customer_id
           ORDER BY order_date DESC, order_id DESC
         ) AS rn
  FROM orders o
)
SELECT *
FROM ranked
WHERE rn = 1;

Running total of orders

SELECT order_id, order_date, amount,
       SUM(amount) OVER (
         ORDER BY order_date, order_id
         ROWS BETWEEN UNBOUNDED PRECEDING
                  AND CURRENT ROW
       ) AS running_total
FROM orders;

Compare each order with the customer’s previous order

SELECT customer_id, order_id, order_date, amount,
       LAG(amount) OVER (
         PARTITION BY customer_id
         ORDER BY order_date, order_id
       ) AS previous_amount
FROM orders;

Other advanced challenges

  1. Find employees whose salary equals their department’s maximum.
  2. Find the median salary.
  3. Calculate month-over-month sales percentage change.
  4. Find customers who ordered in every month of a quarter.
  5. Find products contributing to the first 80% of cumulative sales.
  6. Find employees earning more than every employee in another department.
  7. Find customers who ordered in January but not February.
Know the difference: ROW_NUMBER assigns a unique sequence; RANK leaves gaps after ties; DENSE_RANK does not leave gaps. Decide whether “top three” means three rows or three distinct values.

6. MySQL internals & performance: questions 76–90

#ScenarioWhat to discuss
76A query is slow despite an index.EXPLAIN, selectivity, statistics, access path
77A join unexpectedly returns duplicates.Join cardinality and one-to-many relationships
78An indexed date query scans too many rows.Sargability and date ranges
79Two users update the same balance.Transactions and row locks
80A multi-table update fails partway through.ROLLBACK and atomicity
81Two transactions wait on each other.Deadlocks and retry strategy
82Compare DELETE, TRUNCATE and DROP.Rows vs table; implicit commit
83A comparison involving NULL gives unexpected results.Three-valued logic
84Prevent duplicate email addresses.UNIQUE constraint
85Return the top ten rows after filtering.Clause semantics and ordering
86Choose a composite index.Column order and workload
87NOT IN unexpectedly returns no rows.NULL in subquery
88Investigate a deadlock.InnoDB status and Performance Schema
89Prevent a lost update.Locking or optimistic concurrency
90LIMIT returns different rows.Deterministic ORDER BY

Use EXPLAIN to inspect a query plan

EXPLAIN
SELECT *
FROM orders
WHERE customer_id = 1001;

Review the access type, selected key, estimated rows, join order, and extra operations. On supported MySQL versions, EXPLAIN ANALYZE executes the query and reports actual timing and row counts; use it cautiously for expensive queries.

DELETE vs TRUNCATE vs DROP

  • DELETE: removes selected rows and supports WHERE. Transactional rollback depends on the storage engine and transaction context.
  • TRUNCATE TABLE: removes all rows; in MySQL it is DDL and causes an implicit commit.
  • DROP TABLE: removes the table definition and its data.

Why can NOT IN fail with NULL?

SELECT customer_id
FROM customers
WHERE customer_id NOT IN (
  SELECT customer_id FROM orders
);

If the subquery contains NULL, comparisons can evaluate to UNKNOWN and exclude rows unexpectedly. NOT EXISTS is often a safer alternative.

7. SQL for QA & SDET interviews: questions 91–120

#ScenarioValidation focus
91API returns 100 customers but database has 105.Pagination, filters, authorization, timing
92Validate newly registered user data.Field-level assertions and unique test data
93Order API succeeds but no row is visible.Commit, async processing, environment, logs
94Ensure an order is not charged twice.Idempotency and payment business rules
95Reconcile order total with line items.Quantity, price, tax, discounts, rounding
96Verify database state after an update API.Before/after state and unchanged fields
97Validate a delete API.Soft delete vs physical delete
98Test rollback after a multi-step failure.Atomicity and related tables
99Compare source and target after migration.Counts, keys, fields, aggregates, hashes
100Find source rows missing from target.Anti-join / staged key comparison
101Registration passes but login fails.Stored state, activation, credentials flow
102API status is correct but stored value is wrong.Database assertion against request
103Order completed but payment failed.State consistency and workflow rules
104Validate refund across order, payment and balance.Reconciliation and transactions
105Retry creates duplicate records.Idempotency keys and uniqueness
106Validate audit timestamps.Time zone, precision, update rules
107Concurrent requests create the same customer.Unique constraints and race conditions
108Validate API pagination and sorting.Stable ordering and expected IDs
109Prevent access to another customer’s data.Authorization and tenant boundaries
110Validate a batch job processes eligible rows once.Eligibility, retries, idempotency
111Test foreign-key behavior.Parent/child integrity
112Test a stored procedure.Valid, invalid, boundary inputs
113Test a trigger that writes audit records.Side effects and transaction behavior
114Validate a scheduled job’s date range.Boundaries, time zones, cutoff times
115Test dirty, non-repeatable and phantom reads.Isolation levels
116Connection drops during a transaction.Rollback, retry, uncertain commit
117Test deadlock recovery.Retry handling and consistency
118Validate schema migration.Backward compatibility and data integrity
119Several users buy the last inventory item.Conditional update and locking
120Design reusable SQL assertions for automation.Connection management and test isolation

Example: Find orders without a successful payment

SELECT o.order_id
FROM orders o
WHERE NOT EXISTS (
  SELECT 1
  FROM payments p
  WHERE p.order_id = o.order_id
    AND p.payment_status = 'SUCCESS'
);

Example: Safe inventory decrement

UPDATE inventory
SET stock_quantity = stock_quantity - 1
WHERE product_id = 101
  AND stock_quantity > 0;

Check the affected-row count. A complete checkout should use a transaction for the inventory change and related order records. Also account for the case where the product ID does not exist.

QA interview framework: Explain the business rule, create controlled test data, run the action, query the database, assert expected values, test negative cases, and clean up safely. Never rely on row counts alone to prove data equality.

8. Transactions, constraints & schema design: questions 121–135

#ScenarioTopic
121Prevent duplicate registration emails.UNIQUE
122Prevent orders referencing missing customers.Foreign key
123Prevent a negative account balance.Constraint and transaction logic
124Undo updates after a failure.ROLLBACK
125Undo part of a transaction.SAVEPOINT
126Explain the role of COMMIT.Transaction lifecycle
127Choose isolation for a banking workflow.Isolation levels
128Prevent selling more stock than available.Conditional update and locks
129Compare primary and unique keys.Constraints
130Choose CHAR, VARCHAR, TEXT or numeric types.Data modeling
131Decide whether to normalize tables.Normalization
132Optimize a reporting workload.Denormalization trade-offs
133Add indexes without harming writes unnecessarily.Index trade-offs
134Record changes to customer records.Audit/history design
135Change schema while application is running.Safe migrations

Example: transaction pattern

START TRANSACTION;

UPDATE accounts
SET balance = balance - 500
WHERE account_id = 101
  AND balance >= 500;

-- Application must check affected rows.
-- Apply related updates only when valid.

COMMIT;
-- On a handled failure, use ROLLBACK instead.

For a transfer, debit and credit should happen in one transaction. Use InnoDB, validate affected-row counts, and design locking and retry behavior deliberately.

9. Tricky SQL output questions: 136–150

  1. Why does COUNT(*) differ from COUNT(email)?
  2. How do WHERE and HAVING differ?
  3. What happens when a scalar subquery returns multiple rows?
  4. Why can joining two one-to-many tables inflate totals?
  5. How do UNION and UNION ALL differ?
  6. When is EXISTS preferable to a JOIN?
  7. Why is salary = NULL incorrect?
  8. Why can GROUP BY queries fail with unrelated selected columns?
  9. How do ROW_NUMBER, RANK and DENSE_RANK differ?
  10. Why can a date filter miss records on the final day?
  11. Find the third occurrence of an event for each user.
  12. Find gaps in a sequence of dates.
  13. Why can OR conditions affect query performance?
  14. How can implicit type conversion affect indexes?
  15. Return the latest row per group when timestamps can tie.

COUNT(*) vs COUNT(email)

SELECT COUNT(*) AS total_rows,
       COUNT(email) AS rows_with_email
FROM customers;

COUNT(*) counts all rows. COUNT(email) excludes rows where email is NULL.

UNION vs UNION ALL

SELECT city FROM customers
UNION
SELECT city FROM suppliers;

UNION removes duplicate result rows; UNION ALL preserves them and is generally cheaper when duplicate removal is unnecessary.

10. Interactive practice tracker

Check off each challenge after solving it without help. Your progress is tracked in this page while it remains open.

0 of 15 completed

11. Quick knowledge-check quiz

Choose an answer for each question, then select Check my answers to see your score and explanations.

1. Which expression counts only rows where email is not NULL?
2. Which is often safer for finding rows with no match?
3. Which ranking function does not leave gaps after ties?
4. Which statement is true in MySQL?
5. How should you make a LIMIT query return a stable set of rows?

12. Seven-day SQL interview study plan

Day 1

SQL fundamentals

WHERE, aggregates, NULL, GROUP BY, HAVING.

Day 2

Joins & subqueries

INNER/LEFT JOIN, anti-joins, EXISTS, correlated subqueries.

Day 3

Advanced SQL

CTEs, ranking, running totals, LAG and LEAD.

Day 4

MySQL fundamentals

Indexes, constraints, data types, normalization.

Day 5

Transactions & performance

ACID, locks, deadlocks, EXPLAIN, isolation.

Day 6

QA database testing

API-to-DB checks, migration, reconciliation, concurrency.

Day 7

Mock interview

Solve 15–20 queries without assistance and explain trade-offs.

13. How to answer SQL interview questions confidently

  1. Clarify the rule: What does the business mean by “duplicate”, “latest”, or “successful”?
  2. Design the query: Identify tables, joins, filters, grouping and ordering.
  3. Cover edge cases: Check NULLs, ties, empty results, duplicates and date boundaries.
  4. Consider performance: Discuss indexes and validate the execution plan with EXPLAIN.
  5. Connect it to automation: Explain how your test reads data and asserts the result.
  6. Protect data: Use isolated test data, least-privilege access and safe cleanup.

Ready for your SQL interview?

Start with joins, duplicate detection, second-highest salary, top-N-per-group, EXISTS, window functions, transactions, query tuning, and API-to-database reconciliation. Practise explaining not only what your query does, but why it is correct and how it handles edge cases.

Challenge: Pick five questions today, write the queries from scratch, and explain each solution aloud as if you were in the interview.

Compatibility note: The interactive tracker and quiz use JavaScript. Some WordPress editors or security plugins strip inline scripts from post content. If that happens, keep the article markup in the post and add the script through an approved snippets plugin or your theme’s enqueued JavaScript. Avoid pasting untrusted scripts into production.

Categories Uncategorized Leave a comment

QA Automation Lead Interview Questions and Answers: Management and Leadership

by
QA Automation Lead Interview Preparation: Management Questions & Answers
SQA Tools · Career & Interview Preparation

QA Automation Lead Interview Preparation: Management Questions & Answers

Prepare for leadership interviews with practical scenarios on team management, release pressure, automation architecture, stakeholder communication, quality metrics, and continuous improvement.

60 interview promptsModel answersInteractive practiceSenior QA · Lead · Architect
People leadershipCoaching, delegation, conflict resolution
Quality ownershipRisk, releases, metrics, incidents
Automation strategyFrameworks, CI/CD, AI-assisted testing

For a QA Automation Lead interview, interviewers want to know more than whether you can build a framework or write automation scripts. They want to understand how you lead people, manage delivery, resolve conflicts, make technical decisions, communicate risks, and improve quality across teams.

How to use this guide: Try answering each question aloud before revealing the sample answer. Treat each answer as a template and adapt it to your actual experience. Do not claim results or leadership responsibilities you have not personally demonstrated.

Jump to a section


Practice progress
0 questions practiced

Before your interview

Check off the preparation areas you have completed.

Think like a quality leader

Strong answers explain the situation, the risk, the decision, the trade-offs, the communication, and how you verified the outcome. Use real project examples and measurable evidence whenever possible.

SEO title: QA Automation Lead Interview Questions and Answers — Management, Strategy & Leadership

Categories Uncategorized Leave a comment
by
SQA TOOLS • JAVASCRIPT PRACTICE

JavaScript Loop Programs for Practice

Master JavaScript for loops with 30 hands-on coding exercises, hints, solutions, an interactive code playground and a quick knowledge quiz.

Start Practicing ↓
30Practice Programs
0/30Completed
0%Your Progress

What is a JavaScript for loop?

A for loop repeats a block of code while a condition is true. It is useful for counting, processing arrays, calculating totals and printing patterns.

for (let i = 1; i <= 5; i++) {
  console.log(i);
}

1. Initialization

let i = 1

Sets the starting value.

2. Condition

i <= 5

Checks whether the loop should continue.

3. Increment

i++

Increases the counter by one.

Beginner tip: Always check your starting value, condition and increment to avoid infinite loops.

Practice Exercises

Search a program, select a difficulty and mark each task as completed when you solve it.

You have completed 0 out of 30 programs.

Solutions are provided for learning. Try solving each exercise before revealing the answer.

JavaScript Loop Code Playground

Write your own JavaScript program below and click Run Code to see the output. The playground captures console.log statements.

Output
Your output will appear here.
Safety: Run only code you understand. This simple playground executes JavaScript in your browser page, not in an isolated sandbox. Avoid pasting untrusted code.

Test Your JavaScript Loop Knowledge

Choose an answer for each question and check your score.

1. How many times does this loop run?
for (let i = 0; i < 5; i++) { }
2. Which operator increases a variable by one?
3. Which condition prints numbers from 1 through 10?
4. What does i += 2 do?
5. Which statement exits a loop immediately?

Frequently Asked Questions

What is the difference between for and while loops?

A for loop is convenient when the counting or iteration steps are known. A while loop repeats as long as its condition remains true.

Can I use a for loop to iterate over an array?

Yes. You can use an index-based for loop to visit each array element, or use for…of when you want to iterate over values directly.

How can I avoid an infinite loop?

Make sure the loop condition can eventually become false and that the counter or other relevant state changes as expected.

Do I need to memorize all the solutions?

No. Understand initialization, conditions and updates, then practice writing your own solutions and testing different inputs.

JavaScript Learning Resources • Practice, Experiment and Improve
Created for learners at SQA Tools
```
Categories Uncategorized Leave a comment

TypeScript DataTypes

by
Playwright Tutorial›Module 3: TypeScript for Playwright
Lesson 21 of 9123% through the course

TypeScript Data Types: The Ultimate Beginner-Friendly Guide

Learn TypeScript data types with simple explanations, interactive examples, visual learning, and interview questions.

If you have already learned TypeScript variables, you are ready for the next important step: TypeScript data types.

Don’t let the words “data type” scare you.

A data type is simply a way of telling TypeScript what kind of thing a value is.

Think about a child’s toy room.

You may have a box for cars, a box for dolls, a box for balls, a box for books, and a box for crayons.

You don’t randomly throw everything into every box. You know that a car is a car, a book is a book, and a ball is a ball.

TypeScript works in a similar way.

It helps us describe what kind of information a variable contains.

let name: string = "Rahul";
let age: number = 10;
let isStudent: boolean = true;

Here:

  • name → text
  • age → number
  • isStudent → true/false

Simple definition: A data type tells TypeScript what kind of value something is.

What You Will Learn

By the end of this tutorial, you will understand:

  • What a data type is
  • Why TypeScript needs data types
  • string
  • number
  • boolean
  • Arrays
  • Tuples
  • Objects
  • any
  • unknown
  • null
  • undefined
  • Union types
  • Literal types
  • void
  • never
  • Type inference
  • Type narrowing
  • Real-world examples
  • Playwright examples
  • Interview questions
  • Practice challenges

Meet the TypeScript Type Detective

Before learning individual types, imagine that TypeScript is a little detective.

It looks at a value and asks:

“What are you?”

If it sees:

10

it understands that the value is a number.

If it sees:

"Hello"

it understands that the value is a string.

If it sees:

true

it understands that the value is a boolean.

Every value has a job. The type tells TypeScript what that job is.

Why Do We Need Data Types?

Imagine you have a box.

You put a number inside it:

let age: number = 25;

This tells TypeScript:

“The age variable should contain a number.”

If you later write:

age = "twenty five";

TypeScript can tell you that something is wrong.

Think of TypeScript as a safety guard. It doesn’t stop you from building your application. It simply helps catch many mistakes before they become runtime problems.

TypeScript Data Types at a Glance

Type What It Stores Example
stringText"Hello"
numberNumbers100
booleanTrue/falsetrue
ArrayList of values[1,2,3]
TupleFixed structure["Rahul",10]
ObjectRelated properties{name:"Rahul"}
anyAlmost anythingany
unknownUnknown valueunknown
nullIntentional absencenull
undefinedMissing/unassignedundefined
UnionOne of multiple typesstring | number
voidNo useful returnvoid
neverNever normally completesnever

1. String: The Type for Text

A string represents text.

let name: string = "Rahul";

Other examples:

let firstName: string = "Deepesh";
let city: string = "Indore";
let message: string = "Welcome to TypeScript";
Easy way to remember: String = words or text.

Interactive Example: String

let name: string = "Rahul";

console.log(name);
Try It: Replace "Rahul" with your own name and predict the output.

2. Number: The Type for Numbers

The number type represents numeric values.

let age: number = 10;
let price: number = 999;
let marks: number = 95;
let temperature: number = 32.5;
Interview tip: TypeScript normally uses number for both integer-like and decimal numeric values.

Interactive Number Challenge

let price: number = 100;
let quantity: number = 3;

let total = price * quantity;

console.log(total);

Answer: 300

3. Boolean: The Yes-or-No Type

A boolean has only two values:

true
false
let isLoggedIn: boolean = true;
let testPassed: boolean = false;

Think about a light switch:

ON → true
OFF → false

Interactive Boolean Challenge

const age = 20;

const isAdult = age >= 18;

console.log(isAdult);
Answer: true

4. Arrays: One Box With Many Values

An array stores multiple values.

Imagine five toy cars. Instead of using five separate boxes, you can put them into one larger box.

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

You can also write:

const numbers: Array<number> = [10, 20, 30, 40];

Array Indexes

0 → Apple    1 → Banana    2 → Mango
const fruits: string[] = [
  "Apple",
  "Banana",
  "Mango"
];

console.log(fruits[1]);
Answer: Banana

5. Tuple: A Special Kind of Array

A tuple looks like an array, but each position can have a specific type.

let student: [string, number] = ["Rahul", 10];

That means:

  • Position 0 → string
  • Position 1 → number
Easy memory trick:
Array = many similar things.
Tuple = fixed seats with specific jobs.

6. Object: A Box With Named Compartments

An object groups related information.

const student = {
  name: "Rahul",
  age: 10,
  grade: 5
};

You can access the information using:

console.log(student.name);
console.log(student.age);
Think of an object as a student card: name, age, grade, email, phone number, and other related information all belong together.

7. any: The “Anything Goes” Type

Think of any as a box with no label.

let data: any = "Hello";

data = 100;
data = true;
data = {
  name: "Rahul"
};
Warning: Using any removes much of TypeScript’s type-safety benefit. Use it only when there is a good reason.

8. unknown: The Mystery Box

Imagine receiving a box but not knowing what’s inside.

You shouldn’t immediately pretend it contains a toy car. First, you check.

let data: unknown = "Hello";

if (typeof data === "string") {
  console.log(data.toUpperCase());
}
Remember: unknown means “I don’t know yet. Check me first.”

any vs unknown

Feature any unknown
Can hold any value?YesYes
Type checkingMostly disabledPreserved
Requires narrowing?NoYes
Safer choice?Usually noUsually yes
Easy memory trick:
any → “Do whatever you want.”
unknown → “Check me before you use me.”

9. null: Intentionally Empty

null represents an intentional absence of a value.

let selectedUser = null;

A common real-world example is:

let selectedUser: User | null = null;

This means the variable can contain a User, or intentionally contain no user.

10. undefined: Nothing Has Been Provided

undefined commonly means that a value hasn’t been assigned or isn’t currently available.

let nickname: string | undefined;
Easy memory trick:
null → “I intentionally have no value.”
undefined → “A value hasn’t been provided.”

11. Union Types: This OR That

A union type allows a variable to contain one of several known types.

let userId: string | number;

Both are valid:

userId = 101;

userId = "USER101";

But this is not:

userId = true;
Remember: The | symbol means OR.

Interactive Union Challenge

Which assignments are valid?

let id: string | number;
  1. id = 100;
  2. id = "ABC100";
  3. id = true;
Answer: 1 and 2 are valid. 3 is invalid because boolean isn’t part of the union.

12. Literal Types: Only These Exact Values

A literal type allows only specific values.

let browser: "Chrome" | "Firefox" | "Safari";

These are valid:

browser = "Chrome";
browser = "Firefox";

But:

browser = "Edge";

would not match the declared type.

Real-world use: Literal types are excellent when an application supports a small, known list of values.

13. void: A Function That Returns Nothing Useful

You’ll usually see void as a function return type.

function sayHello(): void {
  console.log("Hello");
}

The function performs an action but doesn’t return a useful value.

14. never: A Function That Never Normally Finishes

never is more advanced.

For example, a function that always throws an error never successfully returns:

function throwError(message: string): never {
  throw new Error(message);
}
Easy memory trick: never means normal completion never happens.

Type Inference: TypeScript Is a Detective

You don’t always have to explicitly specify a type.

const name = "Rahul";
const age = 10;
const passed = true;

TypeScript can infer:

  • name → string
  • age → number
  • passed → boolean

This is called type inference.

Type Narrowing: Let TypeScript Find the Right Type

Sometimes a value can have multiple types.

function printValue(value: string | number) {

  if (typeof value === "string") {
    console.log(value.toUpperCase());
  }

  if (typeof value === "number") {
    console.log(value.toFixed(2));
  }
}

At first TypeScript thinks:

Could be string OR number.

After the typeof check, TypeScript can narrow the value to the appropriate type.

TypeScript Data Types in Playwright

If you are learning TypeScript for Playwright automation, these concepts become very practical.

const baseUrl: string = "https://example.com";

const timeout: number = 30000;

const headless: boolean = true;

const browsers: string[] = [
  "chromium",
  "firefox",
  "webkit"
];

Now your framework clearly communicates what each value represents.

Test Data Example

const username: string = "testuser";
const password: string = "Password123";
const rememberMe: boolean = true;
const retryCount: number = 3;

These values can then be used by your Playwright tests.

Real-World API Example

Imagine an API returns a user ID as either a number or a string.

let userId: string | number;

This is better than using any because you’ve clearly documented the two allowed possibilities.

Common Beginner Mistakes

Mistake 1: Using any Everywhere

Using any simply because TypeScript reports an error defeats much of the purpose of TypeScript.

Mistake 2: Confusing Arrays and Tuples

string[]

means an array of strings.

[string, number]

means a tuple with a string followed by a number.

Mistake 3: Using String Instead of string

Prefer the lowercase primitive type:

let name: string;

rather than using the boxed-object form:

let name: String;

Must-Know TypeScript Data Type Interview Questions

Interview Tip: Don’t just memorize the answer. Understand the simple idea behind it. Interviewers often ask follow-up questions.

1. What are data types in TypeScript?

Answer: Data types describe what kind of value a variable can contain. Examples include string, number, boolean, arrays, tuples, objects, unions, and other TypeScript types.

2. What are the common primitive types?

Answer: Common JavaScript primitive types include string, number, boolean, bigint, symbol, null, and undefined.

3. What is the difference between any and unknown?

Answer: any largely disables type checking for the value. unknown can hold any value, but TypeScript requires you to narrow or check the value before performing type-specific operations.

4. What is a union type?

Answer: A union type allows a value to be one of several possible types.
let id: string | number;

5. What is the difference between an array and a tuple?

Answer: An array generally represents a collection of values, while a tuple describes a fixed structure where each position can have a specific type.

6. What is type inference?

Answer: Type inference is TypeScript automatically determining the type of a value based on its context.
const age = 25;
TypeScript infers age as a number.

7. What is never?

Answer: never represents a value that never occurs. It is commonly used for functions that never successfully complete, such as functions that always throw an error.

8. What is void?

Answer: void is commonly used for functions that do not return a useful value.

9. What is the difference between null and undefined?

Answer: A simple explanation is that null usually represents an intentional absence of a value, while undefined commonly indicates that a value has not been assigned or provided.

10. Does TypeScript have separate integer and floating-point types?

Answer: No. Ordinary numeric values use the number type.

Scenario-Based Interview Questions

Scenario 1: API Returns String or Number

An API sometimes returns:

101

and sometimes:

"101"

Answer:

let userId: string | number;

Scenario 2: Completely Unknown API Response

Would you use any or unknown?

Answer: Prefer unknown when the data is genuinely unknown and you can validate or narrow it before using it.

Scenario 3: Browser Configuration

Your automation framework supports only:

  • chromium
  • firefox
  • webkit

A literal union is a good choice:

type BrowserName =
  | "chromium"
  | "firefox"
  | "webkit";

This prevents accidental values outside the supported list.

Final Interactive Type Detective Challenge

Don’t look at the answer immediately. Try to identify the type of every variable.

const name = "Deepesh";

const age = 35;

const isTrainer = true;

const skills = [
  "Python",
  "Playwright",
  "TypeScript"
];

const employee: [string, number] = [
  "Deepesh",
  35
];

const id: string | number = 101;

Answer

  • name → string
  • age → number
  • isTrainer → boolean
  • skills → string[]
  • employee → [string, number]
  • id → string | number

Practice Programs

Don’t stop after reading. Try these exercises yourself.

Practice 1: Student Information

Create variables for:

  • Name
  • Age
  • Grade
  • Whether the student passed

Practice 2: Shopping Cart

Create variables for product name, price, quantity, and stock status. Calculate the total price.

Practice 3: Employee

Create an employee object containing ID, name, department, experience, and active status.

Practice 4: Browser Configuration

Create a literal union containing chromium, firefox, and webkit.

Practice 5: User ID

Create a variable that accepts a number or string but rejects a boolean.

TypeScript Data Types Cheat Sheet

Type Easy Meaning Example
stringWords/text"Hello"
numberNumbers100
booleanYes/notrue
ArrayList[1,2,3]
TupleFixed positions["A",10]
ObjectRelated information{name:"A"}
anyAnythingany
unknownMystery valueunknown
nullIntentionally emptynull
undefinedNot providedundefined
UnionThis OR thatstring | number
voidNo useful returnvoid
neverNever normally completesnever

Final Takeaway

If you are a beginner, don’t try to memorize every TypeScript type today.

Instead, remember the idea behind each one.

String → text

Number → numbers

Boolean → yes or no

Array → collection

Tuple → fixed structure

Object → related information

any → “Don’t check me.”

unknown → “Check me first.”

Union → “This OR that.”

void → “I don’t return a useful value.”

never → “Normal completion never happens.”

Don’t just read TypeScript. Practice it.

Write code. Break it. Read the error. Fix it. Repeat.

Frequently Asked Questions

What are the most important TypeScript data types for beginners?

Start with string, number, boolean, array, and object. Then learn tuples, unions, any, unknown, null, and undefined. After that, move into literal types, void, never, generics, and utility types.

Is any bad in TypeScript?

any is not forbidden, but excessive use removes much of TypeScript’s type-safety benefit. When a value is genuinely unknown, unknown is often a safer choice.

What is the difference between an array and a tuple?

An array generally represents a collection of values, while a tuple represents a fixed structure where each position can have a specific type.

What is a union type?

A union type allows a value to be one of several possible types. For example, string | number means the value can be a string or a number.

Why is unknown safer than any?

With unknown, TypeScript requires you to check or narrow the value before performing operations that depend on a particular type. With any, those checks are largely bypassed.

Learning Tip: Save this article and revisit the cheat sheet whenever you forget a TypeScript data type. The fastest way to master these concepts is to write small programs and predict their output before running them.

✎ Practise what you learned: 31 TypeScript data types programs with solutions →

← Previous · Lesson 20TypeScript VariablesNext → · Lesson 22TypeScript Operators
View the full Playwright tutorial syllabus
Categories Uncategorized 1 Comment

TypeScript Variables: A Complete Beginner-to-Interview Guide

by
Playwright Tutorial›Module 3: TypeScript for Playwright
Lesson 20 of 9122% through the course

If you are learning TypeScript, one of the first things you need to understand is variables.

Don’t worry if the word “variable” sounds technical.

It is actually a very simple idea.

Imagine you have a small box.

You put a toy inside the box.

Later, you take the toy out and put another toy inside.

That box is similar to a variable in programming.

A variable is simply a named place where your program can store information.

For example:

let name = "Rahul";

Here, name is the variable and "Rahul" is the value stored inside it.

You can imagine it like this:

        Variable
           |
           ↓
    +-------------+
    |    name     |
    |-------------|
    |   "Rahul"   |
    +-------------+

But TypeScript adds something special.

It can also understand what kind of value the variable contains.

For example:

let age: number = 10;

Now TypeScript knows:

“The age variable should contain a number.”

This extra information is one of the reasons TypeScript is so useful for large applications, automation frameworks, and modern web development.

In this tutorial, we will learn TypeScript variables from the absolute beginning and gradually move toward real-world examples, common mistakes, interview questions, and Playwright automation examples.


What You Will Learn

By the end of this article, you will understand:

  • What a variable is
  • Why variables are needed
  • let
  • const
  • var
  • Variable naming rules
  • Type annotations
  • Type inference
  • Reassigning variables
  • Constants
  • Scope
  • Block scope
  • Global scope
  • Variables with objects
  • Variables with arrays
  • Variables in functions
  • Variables in TypeScript automation
  • Common mistakes
  • Interview questions and answers
  • Practice questions

Let’s start from the very beginning.


What Is a Variable?

A variable is a named container for storing a value.

That’s it.

You don’t need a complicated definition.

Think about your school bag.

Your bag may contain:

Books
Pencils
Eraser
Lunch Box
Water Bottle

Similarly, a program needs places to store information.

For example:

let studentName = "Rahul";
let studentAge = 10;
let studentMarks = 95;

We now have three variables.

studentName → "Rahul"

studentAge → 10

studentMarks → 95

You can use these values later.

console.log(studentName);
console.log(studentAge);
console.log(studentMarks);

Output:

Rahul
10
95

So why not simply write "Rahul" everywhere?

Because programs become much easier to manage when we give information meaningful names.

Imagine an application containing 100 different places where a user’s name is required.

Instead of repeatedly writing:

"Rahul"

you can use:

studentName

This makes your program easier to understand and maintain.


Why Do We Need Variables?

Let’s imagine you are creating an online shopping application.

You might need to store:

Product Name
Product Price
Product Quantity
Customer Name
Customer Email
Order Status
Payment Status

Without variables, managing this information would be extremely difficult.

With variables:

const productName = "Laptop";
const price = 75000;
let quantity = 2;
const customerName = "Rahul";
let paymentStatus = "Pending";

Now your program can work with this information.

For example:

const totalPrice = price * quantity;

console.log(totalPrice);

Output:

150000

The important idea is:

Variables allow programs to remember and work with information.


Creating Your First TypeScript Variable

The simplest syntax is:

let variableName = value;

For example:

let name = "Rahul";

Let’s break it down.

let     → tells TypeScript we are creating a variable

name    → variable name

=       → assignment operator

"Rahul" → value

So:

let name = "Rahul";

means:

Create a variable called name and store "Rahul" inside it.


The Three Ways to Declare Variables

TypeScript supports three JavaScript variable declaration keywords:

let
const
var

You will mostly use:

let
const

var is an older JavaScript approach that you may encounter in existing applications.

Let’s understand all three.


Understanding let

Use let when you expect the value to change.

Example:

let age = 20;

age = 21;

console.log(age);

Output:

21

Initially:

age → 20

After reassignment:

age → 21

Think of let as a box whose contents can be replaced.

+----------------+
|      age       |
|----------------|
|      20        |
+----------------+

        ↓

+----------------+
|      age       |
|----------------|
|      21        |
+----------------+

This is useful for values such as:

  • Counters
  • Scores
  • Status
  • Current page
  • Retry count
  • Shopping cart quantity

For example:

let retryCount = 0;

retryCount = retryCount + 1;

console.log(retryCount);

Output:

1

Understanding const

Now imagine you have a box with a special lock.

Once you put something inside, you don’t want to replace the box’s value.

That’s similar to const.

Example:

const country = "India";

You cannot do:

country = "USA";

TypeScript will report an error because a const variable cannot be reassigned.

Use const when the variable should not be reassigned.

For example:

const companyName = "SQA Tools";
const browserName = "Chrome";
const applicationUrl = "https://example.com";

These values are not expected to change during the execution of the program.


let vs const

This is one of the most common interview questions.

The basic difference is:

Featureletconst
Can be reassigned?YesNo
Block scoped?YesYes
Must be initialized immediately?NoYes
Modern JavaScript/TypeScriptYesYes
Common usageChanging valuesValues not reassigned

Example:

let score = 10;

score = 20;

Valid.

But:

const score = 10;

score = 20;

Invalid.

A simple rule:

Use const by default. Use let when you need reassignment.


What Is var?

Before let and const became standard, JavaScript developers commonly used:

var name = "Rahul";

TypeScript still supports var.

However, modern TypeScript development generally prefers:

let

and:

const

Why?

Because var has different scoping behavior that can lead to confusing situations.

For example:

if (true) {
    var message = "Hello";
}

console.log(message);

The variable can still be accessed outside the block.

Compare that with let:

if (true) {
    let message = "Hello";
}

console.log(message);

Here, message is not available outside the block.

This concept is called block scope.

We’ll look at it shortly.


Variable Naming Rules

You can’t give variables just any name you want.

For example:

let studentName = "Rahul";

is valid.

But:

let 123student = "Rahul";

is invalid.

Variable names generally:

  • Can contain letters
  • Can contain numbers
  • Can contain _
  • Can contain $
  • Cannot start with a number
  • Cannot contain spaces
  • Cannot use reserved keywords

For example:

let firstName = "Rahul";
let student1 = "Amit";
let _count = 10;
let $price = 500;

These are valid syntax.


Use Meaningful Variable Names

This is one of the most important programming habits.

Avoid:

let x = "Rahul";
let y = 25;
let z = true;

Someone reading the code may wonder:

What is x?

Instead:

let userName = "Rahul";
let userAge = 25;
let isLoggedIn = true;

Now the code explains itself.

Compare:

if (x) {
    // ...
}

with:

if (isLoggedIn) {
    // ...
}

Which one is easier to understand?

Obviously:

isLoggedIn

Good variable names are like good labels on boxes.


Camel Case in TypeScript

A common naming convention in TypeScript is camelCase.

Examples:

firstName
lastName
userName
emailAddress
phoneNumber
isLoggedIn
totalAmount
productPrice

The first word starts with lowercase, and the next words start with uppercase.

For example:

firstName
     ↑
   capital N

This convention is widely used in JavaScript and TypeScript codebases.


Type Annotations

Now let’s add the feature that makes TypeScript different from ordinary JavaScript.

You can explicitly tell TypeScript what type of value a variable should contain.

Example:

let age: number = 25;

The : number part is called a type annotation.

Another example:

let name: string = "Rahul";

And:

let isActive: boolean = true;

Think of this as putting a label on the box.

+---------------------+
| age                 |
|---------------------|
| TYPE: number        |
| VALUE: 25           |
+---------------------+

Now TypeScript understands exactly what you expect.


TypeScript Variables With Common Types

String

let userName: string = "Rahul";

A string stores text.


Number

let age: number = 25;

A number stores numeric values.


Boolean

let isLoggedIn: boolean = true;

A boolean stores:

true

or:

false

Type Inference

Here’s a very useful TypeScript feature.

You don’t always have to tell TypeScript the type.

For example:

let name = "Rahul";

TypeScript looks at "Rahul" and understands:

name → string

Similarly:

let age = 25;

TypeScript understands:

age → number

And:

let isActive = true;

TypeScript understands:

isActive → boolean

This is called type inference.

Think of TypeScript as a smart teacher.

You don’t need to tell the teacher:

“This is a red apple.”

The teacher can see the apple and understand what it is.

Similarly, TypeScript can often look at the initial value and determine the type.


Type Annotation vs Type Inference

Compare these:

let age: number = 25;

and:

let age = 25;

In many situations, TypeScript can infer that age is a number.

So you don’t necessarily need to write:

: number

every time.

A clean approach is:

const name = "Rahul";
const age = 25;
const isActive = true;

TypeScript understands their types automatically.


When Should You Use Type Annotations?

Type annotations are useful when they provide additional clarity or when TypeScript cannot infer the intended type.

For example:

let userName: string;

Later:

userName = "Rahul";

You can also use them when defining function parameters, object structures, API models, and other important contracts.

For example:

let userId: string | number;

Here the annotation communicates something important:

userId can be a string or a number.


Reassigning Variables

A variable declared with let can be reassigned.

let city = "Indore";

city = "Mumbai";

console.log(city);

Output:

Mumbai

But you cannot reassign a const variable:

const city = "Indore";

city = "Mumbai";

This produces an error.

The important word here is reassignment.


A Common const Confusion

Many beginners think:

“If I use const, absolutely nothing can change.”

That’s not completely correct.

Consider:

const user = {
    name: "Rahul",
    age: 25
};

You cannot replace the entire object:

user = {
    name: "Amit",
    age: 30
};

But you can potentially change a property:

user.name = "Amit";

Why?

Because const prevents reassignment of the variable binding.

It does not automatically make every object property immutable.

If you need that behavior, TypeScript provides other tools such as readonly.


Variables With Arrays

Variables can store arrays too.

const fruits: string[] = [
    "Apple",
    "Banana",
    "Mango"
];

You can access values:

console.log(fruits[0]);

Output:

Apple

You can also modify the array:

fruits.push("Orange");

The variable is still const.

Why?

Because we aren’t replacing the array itself.

We are modifying its contents.

This distinction is important when explaining const during interviews.


Variables With Objects

A variable can also store an object.

const user = {
    name: "Rahul",
    age: 25
};

You can access:

console.log(user.name);

Output:

Rahul

You can also create more complex objects:

const employee = {
    id: 101,
    name: "Rahul",
    department: "QA",
    experience: 8
};

This becomes especially important when we start learning interfaces.


Variables Inside Functions

Variables can be created inside functions.

function calculateTotal() {
    const price = 100;
    const quantity = 2;

    return price * quantity;
}

Here:

price
quantity

are local variables.

They exist inside the function.

You can think of a function as a room.

Variables created inside the room normally belong to that room.


Understanding Scope

Scope means where a variable can be accessed.

This is an important concept for interviews.

Consider:

let globalName = "Rahul";

function printName() {
    console.log(globalName);
}

The function can access globalName.

But consider:

function printName() {
    let name = "Rahul";
}

console.log(name);

This will not work because name exists only inside the function.


Block Scope

let and const are block scoped.

A block is usually represented by:

{
    // block
}

For example:

if (true) {
    let message = "Hello";

    console.log(message);
}

This works.

But:

if (true) {
    let message = "Hello";
}

console.log(message);

The second console.log() cannot access message.

The variable belongs to the block.


Why Scope Matters in Automation?

If you work with Playwright and TypeScript, scope becomes very important.

For example:

test("Login Test", async ({ page }) => {

    const username = "admin";

    await page.getByLabel("Username").fill(username);

});

Here, username belongs to that test block.

You cannot automatically expect it to be available everywhere else.

Understanding scope helps you avoid bugs related to variables being unavailable where you need them.


TypeScript Variables in Playwright

Now let’s connect the concept to something practical.

Suppose you are writing a Playwright test.

You might have:

const url = "https://example.com";

const username = "testuser";

const password = "Password123";

Then:

await page.goto(url);

await page.getByLabel("Username").fill(username);

await page.getByLabel("Password").fill(password);

Variables make your test much easier to maintain.

Imagine the URL changes.

Instead of searching through 50 test cases for:

https://example.com

you can update one variable.

This is one of the practical benefits of using meaningful variables.


Variables for Test Configuration

You might create:

const browserName = "chromium";

const baseUrl = "https://example.com";

const timeout = 30000;

const headless = true;

Now these values can be reused across your framework.

For example:

test.setTimeout(timeout);

This makes automation frameworks more configurable and easier to maintain.


Variables for Test Data

You can also create test data:

const username = "testuser";

const password = "Password123";

const expectedTitle = "Dashboard";

Then:

await page.getByLabel("Username").fill(username);

await page.getByLabel("Password").fill(password);

await expect(page).toHaveTitle(expectedTitle);

This is much cleaner than putting values directly into every statement.


Variables With Environment Values

In real automation frameworks, sensitive information should not normally be hardcoded directly into the test.

Instead of:

const password = "MyRealPassword";

you might read configuration from environment variables:

const password = process.env.PASSWORD;

Now your test can use an environment-specific value.

For example:

Development → Development password

QA → QA password

Staging → Staging password

Production → Production credentials

This is an important concept when you move from beginner automation scripts to professional frameworks.


Common Mistakes With TypeScript Variables

Mistake 1: Using var Everywhere

Avoid writing modern TypeScript like this:

var name = "Rahul";

Prefer:

const name = "Rahul";

or:

let name = "Rahul";

depending on whether reassignment is needed.


Mistake 2: Using let When const Is Better

Instead of:

let companyName = "ABC";

if you never reassign it, prefer:

const companyName = "ABC";

This communicates your intention more clearly.


Mistake 3: Poor Variable Names

Avoid:

let a = "Rahul";
let b = 25;
let c = true;

Prefer:

const userName = "Rahul";
const userAge = 25;
const isActive = true;

Mistake 4: Using any Without a Reason

Avoid:

let data: any;

when you know what the data should contain.

TypeScript’s biggest advantage is type safety.

Don’t throw that advantage away unnecessarily.


TypeScript Variable Interview Questions and Answers

Now let’s move into the part that many learners really need.

Interview preparation.

Understanding a topic is one thing.

Explaining it confidently during an interview is another.

Let’s practice.


Interview Question 1: What is a variable in TypeScript?

Answer:

A variable is a named container used to store a value.

For example:

let age = 25;

Here, age is the variable and 25 is its value.

TypeScript can also associate a type with the variable:

let age: number = 25;

Interview Question 2: What are the different ways to declare variables in TypeScript?

Answer:

TypeScript supports:

var
let
const

However, modern TypeScript development generally prefers let and const.

let is used when a variable may be reassigned, while const is used when the variable should not be reassigned.


Interview Question 3: What is the difference between let and const?

Answer:

The main difference is reassignment.

Example:

let count = 10;

count = 20;

This is valid.

But:

const count = 10;

count = 20;

This causes an error because const variables cannot be reassigned.

Both let and const are block scoped.


Interview Question 4: What is the difference between var, let, and const?

Answer:

var is function scoped, while let and const are block scoped.

let allows reassignment.

const does not allow reassignment after initialization.

Example:

let score = 10;
score = 20;

Valid.

const score = 10;
score = 20;

Invalid.

For modern TypeScript development, let and const are generally preferred over var.


Interview Question 5: What is type inference?

Answer:

Type inference is TypeScript’s ability to automatically determine the type of a variable from its value.

Example:

let name = "Rahul";

TypeScript automatically infers:

name → string

Similarly:

let age = 25;

is inferred as:

age → number

This means explicit type annotations are not always necessary.


Interview Question 6: What is a type annotation?

Answer:

A type annotation explicitly specifies the expected type of a variable.

Example:

let age: number = 25;

Here, : number is the type annotation.

Another example:

let name: string = "Rahul";

Interview Question 7: Can a const object be modified?

Answer:

Yes, its properties can generally be modified.

For example:

const user = {
    name: "Rahul"
};

user.name = "Amit";

This is allowed.

However, reassigning the entire object is not allowed:

user = {
    name: "Amit"
};

const prevents reassignment of the variable binding; it does not automatically make the object’s contents immutable.


Interview Question 8: What is variable scope?

Answer:

Scope defines where a variable can be accessed.

For example:

function test() {
    const name = "Rahul";
}

The name variable is available inside the function but not outside it.

Similarly, let and const are block scoped.


Interview Question 9: Why should we prefer const over let when possible?

Answer:

Because const communicates that the variable will not be reassigned.

For example:

const browser = "Chrome";

This tells other developers that browser is not expected to point to another value.

It also reduces accidental reassignment.


Interview Question 10: Why is var generally avoided in modern TypeScript?

Answer:

var has function-level scoping and behavior that can make code harder to reason about.

let and const provide block scope and clearer rules around reassignment.

Therefore, modern TypeScript code generally prefers let and const.


Scenario-Based Interview Questions

Now let’s make the questions more realistic.

These are the kinds of questions that can separate a beginner from someone who understands the concept.


Scenario 1: Which declaration would you use?

You have:

const baseUrl = "https://example.com";

The URL never changes.

Would you use let or const?

Answer:

Use:

const baseUrl = "https://example.com";

because the variable does not need reassignment.


Scenario 2: Retry Counter

You have:

let retryCount = 0;

Every time an API request fails, you increase the counter.

Would you use let or const?

Answer:

Use:

let retryCount = 0;

retryCount++;

because the value changes.


Scenario 3: Playwright Browser

You have:

const browserName = "chromium";

The browser name does not change during the test.

Which declaration should you use?

Answer:

const.

const browserName = "chromium";

Quick Interview Revision

Before an interview, remember these five points:

1. Variable = named storage location

2. let = can be reassigned

3. const = cannot be reassigned

4. var = older function-scoped declaration

5. Type inference = TypeScript automatically determines the type

If you understand these five points, you already have a strong foundation.


Practice Programs

Don’t stop after reading.

Try writing these programs yourself.

Program 1

Create variables for:

Name
Age
City
IsStudent

Use appropriate types.


Program 2

Create a variable called score.

Start it with:

50

Then change it to:

100

Use the correct variable declaration.


Program 3

Create an array containing five programming languages.

Example:

const languages: string[] = [
    "JavaScript",
    "TypeScript"
];

Add three more.


Program 4

Create an object representing a QA engineer.

Include:

name
experience
primarySkill
isAutomationEngineer

Program 5

Create variables representing a Playwright test:

baseUrl
username
password
timeout
browser

Choose appropriate variable declarations.


Mini Challenge

Can you predict the output?

let score = 10;

score = 20;

console.log(score);

Answer:

20

Now try:

const score = 10;

score = 20;

console.log(score);

What happens?

The program produces a TypeScript error because score is declared using const and cannot be reassigned.


TypeScript Variables Cheat Sheet

Keyword/ConceptMeaning
letVariable that can be reassigned
constVariable that cannot be reassigned
varOlder function-scoped declaration
Type annotationExplicitly defines a type
Type inferenceTypeScript automatically determines the type
ScopeWhere a variable can be accessed
Block scopeScope inside {}
stringText
numberNumeric value
booleantrue or false

Final Takeaway

Variables are one of the smallest concepts in TypeScript, but they are also one of the most important.

Every time your application remembers something, there is a good chance a variable is involved.

A username?

Variable.

A password?

Variable.

A product price?

Variable.

A Playwright URL?

Variable.

A test timeout?

Variable.

A retry counter?

Variable.

Once you understand variables, you have started learning the language’s basic building blocks.

The most important rules to remember are simple:

const name = "Rahul";

Use const when you don’t need reassignment.

let score = 10;
score = 20;

Use let when the value needs to change.

And:

let age: number = 25;

Use a type annotation when you want to explicitly communicate the expected type.

Finally, remember this:

Don’t learn TypeScript by memorizing syntax. Learn it by understanding why the syntax exists.

When you understand that a variable is simply a named place for storing information, let, const, types, scope, and type inference become much easier.

And this is only the beginning.

The next step is to understand TypeScript data types, where we will explore strings, numbers, booleans, arrays, tuples, objects, unions, any, unknown, null, undefined, and practical examples.


Frequently Asked Questions

1. What is a variable in TypeScript?

A variable is a named container used to store a value.

let age = 25;

Here, age stores the value 25.


2. Should I use let or const?

Use const by default when you don’t need to reassign the variable.

Use let when the value needs to change.

const name = "Rahul";

let score = 10;
score = 20;

3. Is var still supported in TypeScript?

Yes, TypeScript supports var because it is part of JavaScript.

However, modern TypeScript code generally prefers let and const because they provide clearer block-scoping behavior.


4. What is type inference in TypeScript?

Type inference is when TypeScript automatically determines the type of a value.

const name = "Rahul";

TypeScript understands that name is a string without you explicitly writing:

const name: string = "Rahul";

5. Can a const object be changed?

The object variable itself cannot be reassigned:

const user = {
    name: "Rahul"
};

But its properties can generally be changed:

user.name = "Amit";

If you need properties that cannot be reassigned, TypeScript provides features such as readonly.


✎ Practise what you learned: 31 TypeScript variables programs with solutions →

← Previous · Lesson 19Promises and Async/Await for PlaywrightNext → · Lesson 21TypeScript Data Types
View the full Playwright tutorial syllabus
Categories Uncategorized Tags JavaScript TypeScript, Learn TypeScript, Learn TypeScript Step by Step, TypeScript, TypeScript Arrays, TypeScript Basics, TypeScript Beginner Guide, TypeScript Best Practices, TypeScript Coding, TypeScript Data Types, TypeScript Data Types Examples, TypeScript Development, TypeScript Examples, TypeScript Examples for Beginners, TypeScript for Beginners, TypeScript Interface, TypeScript Interface Examples, TypeScript Interfaces, TypeScript let const var, TypeScript Objects, TypeScript Programming, TypeScript Programming Tutorial, TypeScript Tutorial, TypeScript Tutorial for Beginners, TypeScript Type Annotation, TypeScript Type Inference, TypeScript Types, TypeScript Union Types, TypeScript Variables 3 Comments

AI + QA Automation Interview Questions and Answers | Playwright, MCP & AI Agents

by
Playwright Tutorial›Module 15: Interview Preparation
Lesson 90 of 9199% through the course

Senior QA / SDET / Automation Architect Interview Preparation

AI is rapidly becoming an important part of modern QA automation. Senior QA engineers and SDETs are increasingly expected to understand not only traditional automation frameworks such as Playwright and Selenium, but also AI-assisted testing, AI agents, MCP, intelligent test generation, debugging, and self-healing automation.

This guide contains practical AI + QA Automation interview questions and answers designed for Senior QA Engineers, SDETs, QA Automation Architects, and professionals working with Playwright and AI-assisted automation.

Interview Tip: AI should be presented as an engineering accelerator, not a replacement for QA engineering judgment. The QA engineer remains responsible for business validation, test coverage, risk assessment, and final automation quality.

1. How Are You Using AI in Your Day-to-Day QA Automation Work?

Interview Answer

I use AI as an engineering assistant across the complete QA lifecycle rather than only for generating automation scripts. I use it for requirement analysis, test-case generation, identifying edge cases, creating Playwright scripts, locator suggestions, debugging failures, improving existing automation, generating test data, code reviews, and documentation.

In my Playwright automation work, I also use AI with Playwright MCP to allow an AI agent to interact with the application, explore the UI, understand the actual behavior, identify locators, and then generate or refine automation code.

I do not blindly trust AI-generated output. I treat AI as an accelerator while the QA engineer remains responsible for validating business logic, test coverage, framework standards, and final automation quality.

Key Points

  • Requirement analysis
  • Test design and edge cases
  • Playwright automation
  • Locator identification
  • Failure analysis and debugging
  • Refactoring and code review
  • Test data generation
  • Documentation

2. How Do You Use AI to Generate Test Cases From a Requirement?

Interview Answer

I provide the requirement, acceptance criteria, business rules, and relevant application context. I ask AI to analyze the requirement and generate positive, negative, boundary, accessibility, integration, error-handling, and regression scenarios.

I then review the generated cases against the actual business requirements because AI can produce technically valid but business-incorrect scenarios. After validation, I categorize the cases and decide which scenarios should be automated.

Example

For a subscription purchase workflow, AI can help identify scenarios such as:

  • Valid payment
  • Invalid card
  • Expired card
  • Payment failure
  • Session timeout
  • Address boundary conditions
  • Keyboard navigation
  • Screen-reader behavior
  • Security of sensitive data

A useful practice is to ask AI to clearly distinguish between explicit requirements and assumptions.


3. How Do You Use AI With Playwright to Generate Automation Scripts?

Interview Answer

I use a structured workflow instead of simply asking AI to generate a Playwright test.

First, I provide the requirement and scenario. Then I allow the AI to understand the application and existing framework. Where appropriate, I use Playwright MCP to explore the application and identify actual UI elements and behavior.

AI can then generate the Playwright test using existing page objects, fixtures, utilities, assertions, and coding conventions.

I execute the test, analyze failures, refine the implementation, and perform a human review before committing the code.

AI-Assisted Playwright Workflow

Requirement
    ↓
AI Analysis
    ↓
Test Scenario
    ↓
Browser Exploration
    ↓
Locator Discovery
    ↓
Code Generation
    ↓
Test Execution
    ↓
Failure Analysis
    ↓
Refinement
    ↓
Human Review

What Should You Validate?

  • Fixtures
  • Page objects
  • Test data
  • Assertions
  • Cleanup
  • Stable locators
  • Framework conventions

4. What Is Playwright MCP and How Does It Help QA Automation?

Interview Answer

Playwright MCP is an MCP-based integration that allows an AI agent to interact with a browser through Playwright capabilities.

Instead of relying only on static source code, the agent can explore the application, inspect the UI, perform browser actions, observe results, and use that context to generate or refine Playwright automation.

Traditional AI Automation

Requirement + Code/Context
          ↓
     Generated Script

MCP-Assisted Automation

Requirement
     ↓
AI Agent
     ↓
Browser Exploration
     ↓
Inspect Elements
     ↓
Perform Actions
     ↓
Observe Results
     ↓
Generate/Refine Test

Playwright MCP improves application interaction, but it does not automatically provide business understanding. Requirements and business rules remain essential.


5. What Are Playwright Planner, Generator and Healer?

Interview Answer

These can be understood as three stages of an AI-assisted automation workflow.

Planner

The Planner determines what should be tested. It analyzes requirements, acceptance criteria, and user journeys to create a test plan.

Generator

The Generator determines how the test should be automated. It converts the test plan into Playwright tests, locators, assertions, and supporting code.

Healer

The Healer analyzes why automation failed and proposes a repair. For example, it may identify changed locators or synchronization issues.

Important: AI-based healing should be treated as a proposed fix that requires validation. Automatic healing can otherwise hide a real application regression.

6. How Do You Use AI When a Playwright Test Fails?

Interview Answer

I use AI as a debugging assistant. I provide the test code, failure message, stack trace, screenshot, trace information, and relevant application behavior.

AI can help classify the failure as an application defect, locator problem, synchronization issue, test-data issue, environment problem, or automation defect.

I reproduce and validate the proposed fix before accepting it.

Information I Analyze

  • Stack trace
  • Playwright trace
  • Screenshot
  • DOM snapshot
  • Console errors
  • Network failures
  • Test code

The objective is root-cause analysis, not merely producing a passing test.

Avoid using arbitrary hard waits such as waitForTimeout() simply to make a test green.


7. Can AI Completely Automate Testing? What Are Its Limitations?

Interview Answer

AI can automate a significant portion of repetitive QA activities, but completely autonomous testing is risky.

Testing requires business understanding, risk assessment, exploratory thinking, and judgment. AI can misunderstand requirements, hallucinate APIs or locators, generate redundant tests, create incorrect assertions, or incorrectly heal tests.

Major Limitations of AI in Testing

  • Business-context limitations
  • Hallucinations and incorrect assumptions
  • False confidence from realistic-looking tests
  • Incorrect assertions
  • Maintenance and self-healing risks
  • Exploratory and usability testing limitations
  • Security and privacy concerns
  • Dependence on the quality of provided context

8. How Do You Validate AI-Generated Test Cases or Automation Code?

Interview Answer

I use a human-in-the-loop validation process.

I validate the AI-generated output against requirements, acceptance criteria, business rules, framework standards, and existing automation patterns.

Then I execute the test and verify that the assertions actually prove the intended behavior.

Validation Checklist

  • Requirement coverage
  • Positive scenarios
  • Negative scenarios
  • Boundary scenarios
  • Locator stability
  • Meaningful assertions
  • Synchronization
  • Framework compatibility
  • Test isolation
  • Data management
  • Cleanup
  • Security
  • CI execution
  • Flakiness

A particularly important validation is to run the test repeatedly and verify that the test actually fails when the intended application behavior is broken.


9. Which AI Tools Have You Used in QA Automation?

Interview Answer

I have worked with AI coding assistants such as GitHub Copilot and Claude-based coding workflows, and I have explored AI-assisted browser automation using Playwright MCP.

I use different tools for different parts of the QA lifecycle rather than expecting one tool to solve everything.

AI Tool Typical Usage
GitHub Copilot Code generation, refactoring and test generation
Claude Code Repository-level reasoning, codebase analysis and implementation assistance
Playwright MCP Browser exploration and AI-assisted Playwright automation
ChatGPT Requirement analysis, test design, debugging and documentation
AI Agents Multi-step exploration and automation workflows
Interview Tip: Only mention AI tools that you can genuinely describe and demonstrate from your own experience.

10. Give Me a Real Project Example Where AI Improved Your QA Automation Process

Interview Answer

In a web QA automation project, I worked on subscription and checkout-related functionality involving user journeys such as subscription selection, checkout, payment, cancellation, and switching plans.

I used AI-assisted automation to accelerate test design and Playwright implementation. I provided requirements and acceptance criteria to identify important journeys and edge cases, used browser exploration to understand the actual UI and locators, and used AI to generate an initial Playwright implementation following the existing framework.

After execution, I used AI to analyze failures and distinguish application issues from automation, synchronization, test-data, or environment problems.

The improvement was not simply generating code faster. AI reduced repetitive analysis, scaffolding, locator discovery, debugging, and documentation while I remained responsible for business validation and final test quality.

Important Interview Tip

Do not invent productivity percentages. If asked for metrics, provide measured figures only.

Useful metrics include:

  • Automation development time
  • Maintenance effort
  • Execution stability
  • Defect coverage

11. How Would You Integrate GitHub Copilot or Claude Code With Your VS Code Project?

Interview Answer

I would not expect an AI coding assistant to understand the framework simply because it is installed in VS Code.

I would provide structured repository context and engineering instructions. I would make framework conventions explicit through documentation, architecture guidance, coding standards, test examples, and the supported repository instruction mechanism for the chosen AI tool.

I would also ask the agent to inspect the repository before generating code.

Repository Areas to Analyze

  • package.json
  • playwright.config.ts
  • Tests
  • Page objects
  • Fixtures
  • Utilities
  • Configuration
  • Test data

Framework Information to Document

  • Automation architecture
  • Page-object guidelines
  • Locator strategy
  • Authentication approach
  • Test-data strategy
  • Coding standards

After implementation, validate the generated code using linting, TypeScript compilation, Playwright execution, code review, and CI.


12. If Copilot Generates an Automation Script, What Would You Validate?

Interview Answer

I would validate the requirement coverage, test objective, locator quality, assertions, synchronization, framework compatibility, test isolation, test data, cleanup, error handling, maintainability, security, CI compatibility, and flakiness.

Validation Checklist

  • Requirement and business behavior
  • Stable locators
  • Meaningful assertions
  • No unnecessary hard waits
  • Reuse of fixtures, page objects and utilities
  • Independent test execution
  • Reliable test data and cleanup
  • No secrets or sensitive data
  • Repeated execution
  • CI execution
  • Failure behavior
  • Maintainability

13. How Would You Leverage Copilot to Understand Your Framework and Codebase?

Interview Answer

I would use Copilot as a codebase analysis assistant before using it as a code generator.

I would ask it to map the architecture, test execution flow, authentication mechanism, fixtures, page objects, utilities, configuration, and existing patterns.

Once it understands those relationships, I would ask it to identify the best existing components to reuse before creating new code.

Useful Questions to Ask the AI Assistant

  • Explain the automation architecture.
  • Trace how a Playwright test moves from the spec through fixtures, configuration and browser initialization.
  • Identify the authentication fixture for a new authenticated test.
  • Find page objects and their public methods.
  • Identify the recommended test-data approach.
  • Find similar existing tests and explain their common pattern.
  • Identify impacted tests before modifying a shared page object.
  • Detect duplicate utilities before creating new ones.

Advanced: AI-Powered QA Automation Architecture

A senior-level AI-powered QA automation architecture can be represented as follows:

Requirement
    ↓
AI Test Planner
    ↓
Test Scenarios
    ↓
Browser Exploration / Playwright MCP
    ↓
AI Test Generator
    ↓
Playwright Tests
    ↓
Execution
    ↓
 ┌───────────────┐
 │               │
PASS            FAIL
 │               │
 ↓               ↓
CI/CD       AI Debugger / Healer
                    ↓
              Proposed Fix
                    ↓
              Human Review
                    ↓
                  CI/CD

The objective is intelligent, context-aware and maintainable automation rather than blindly autonomous testing.

Key Interview Language for AI + QA Roles

  • Human-in-the-loop
  • Requirement-to-test automation
  • Context-aware code generation
  • Repository-level understanding
  • Browser exploration
  • Root-cause analysis
  • Self-healing / AI-assisted healing
  • Test coverage optimization
  • Framework-aware automation
  • AI as an accelerator, not a replacement

60-Second AI + QA Interview Introduction

I have been using AI as an engineering accelerator across different stages of the QA automation lifecycle, rather than using it only for code generation.

For requirement analysis, I use AI to identify test scenarios, edge cases, negative scenarios and potential risks. With Playwright automation, I use AI-assisted workflows and Playwright MCP to explore the application, understand the UI, identify locators and generate automation code based on actual application behavior.

I also use AI for debugging Playwright failures by analyzing stack traces, screenshots, traces, locators and application behavior.

For repository-level development, I use AI coding assistants such as GitHub Copilot or Claude-based workflows to understand existing framework patterns, fixtures, page objects and utilities before generating new automation.

The key principle I follow is human-in-the-loop automation: I validate requirements, business logic, test coverage, locator stability, assertions, framework compatibility and execution results before committing automation.


Strong Closing Statement for the Interview

My approach is to use AI to reduce repetitive engineering effort while increasing the depth and speed of testing.

I use AI for requirement analysis, test design, automation generation, browser exploration, debugging, code review and documentation.

With Playwright and MCP-based workflows, I can move toward a more agentic approach where the system can understand a requirement, explore the application, create a test plan, generate automation and analyze failures.

However, I don’t consider AI-generated output automatically trustworthy. The QA engineer still owns business validation, risk assessment, test coverage and the final quality of the automation.

The goal is intelligent, context-aware and maintainable automation with humans making the important quality decisions.


Final Takeaway

For senior QA and SDET interviews, the important message is not simply that you know how to use AI to generate test scripts.

You should be able to explain how AI fits into the complete QA lifecycle:

  1. Understand the requirement.
  2. Generate and analyze test scenarios.
  3. Explore the application.
  4. Identify reliable locators.
  5. Generate framework-compatible automation.
  6. Execute the tests.
  7. Analyze failures.
  8. Propose fixes.
  9. Validate the fixes.
  10. Maintain high-quality automation through CI/CD.

The strongest approach is to combine AI capabilities with QA engineering expertise. AI can accelerate repetitive engineering activities, but human judgment remains essential for business validation, risk assessment, test coverage, and quality.

← Previous · Lesson 89Interview Questions for Experienced (2, 3, 5+ Years)Next → · Lesson 91Playwright Cheat Sheet
View the full Playwright tutorial syllabus
Categories Uncategorized Tags AI Agents, AI Testing, GitHub Copilot, Playwright, Playwright MCP, QA Automation, SDET, Test Automation 2 Comments

200 Playwright + TypeScript Interview Questions and Answers

by
Playwright Tutorial›Module 15: Interview Preparation
Lesson 88 of 9197% through the course

Below is a full set of 100 interview questions with answers, organized by topic. Each answer is written to be interview-ready.


Section 1: Fundamentals & Architecture (Q1–Q15)

Q1. What is Playwright?
Playwright is an open-source end-to-end testing framework by Microsoft that drives Chromium, Firefox, and WebKit through a single API. It supports auto-waiting, network interception, multiple browser contexts, and cross-language bindings including TypeScript.

Q2. Why use TypeScript with Playwright?
TypeScript gives static typing for locators, fixtures, and page objects, catching errors at compile time. It also provides IDE autocompletion for the Playwright API and makes refactoring safer in large suites.

Q3. What are the core components of Playwright?
Browser, BrowserContext, Page, Locator, Frame, Request/Response, and the Test Runner (@playwright/test).

Q4. Difference between Playwright and Selenium?
Playwright communicates with browsers via the DevTools protocol and its own patched builds, giving auto-waiting, network interception, and multi-context isolation out of the box. Selenium relies on WebDriver and generally requires explicit waits and separate drivers per browser.

Q5. What is auto-waiting?
Before performing an action, Playwright waits for the element to be attached, visible, stable, enabled, and to receive events. This eliminates most manual sleep calls.

Q6. What browsers does Playwright support?
Chromium (Chrome/Edge), Firefox, and WebKit (Safari engine).

Q7. What is a BrowserContext?
An isolated session inside a browser with its own cookies, localStorage, and cache. It behaves like an incognito window and is the unit of test isolation.

Q8. Can one browser have multiple contexts?
Yes. One browser instance can host many contexts, each fully isolated, enabling parallel tests and multi-user scenarios.

Q9. What is the difference between page and context?
A context is the session; a page is a single tab within that session. Multiple pages can exist in one context and share cookies.

Q10. What is a Locator?
A Locator is a lazy reference to an element that is re-resolved on every action, so it never goes stale after a re-render.

Q11. Difference between Locator and ElementHandle?
Locators are auto-waiting, re-resolved, and preferred. ElementHandles are static references that can become detached and are considered legacy.

Q12. What test runners can be used with Playwright?
The official @playwright/test runner, plus Jest, Mocha, Vitest, or Cucumber if configured manually.

Q13. What is playwright.config.ts?
The central configuration file defining test directory, timeouts, retries, reporters, projects, and global setup/teardown.

Q14. How do you run a single test file?
npx playwright test tests/login.spec.ts

Q15. How do you run tests in headed mode?
npx playwright test --headed


Section 2: Locators & Selectors (Q16–Q30)

Q16. Why prefer getByRole() over CSS selectors?
It targets elements by ARIA role and accessible name, matching how users perceive the page. It survives DOM and class-name refactoring.

Q17. List Playwright’s built-in locators.
getByRole, getByText, getByLabel, getByPlaceholder, getByAltText, getByTitle, getByTestId, and locator() for CSS/XPath.

Q18. How do you locate by test ID?
page.getByTestId('submit-btn') — the attribute is configurable via testIdAttribute in config.

Q19. Difference between getByText and getByRole?
getByText matches visible text; getByRole matches semantic role plus optional name. Role-based is more robust.

Q20. How do you chain locators?
page.getByRole('row').filter({ hasText: 'Alice' }).getByRole('button', { name: 'Edit' })

Q21. What does .filter({ hasText }) do?
Narrows a locator to elements containing the given text.

Q22. How do you select the nth element?
locator.nth(0) for the first, locator.first(), locator.last().

Q23. What is locator.all()?
Returns an array of all matching elements resolved at call time — useful for iteration but not for auto-waiting.

Q24. How do you handle strict mode violations?
Make the locator more specific, or use .first(), .nth(), or .filter().

Q25. What is strict mode?
Playwright throws if a locator resolves to more than one element during an action, preventing ambiguous interactions.

Q26. Can you use XPath?
Yes: page.locator('xpath=//button[@id="save"]'), though it’s discouraged.

Q27. How do you locate inside an iframe?
page.frameLocator('#frame').getByRole('button')

Q28. How do you locate inside shadow DOM?
Playwright pierces open shadow DOM automatically with CSS locators.

Q29. What is locator.waitFor()?
Waits for the locator to reach a state: attached, detached, visible, or hidden.

Q30. How do you count matching elements?
await locator.count()


Section 3: Actions & Assertions (Q31–Q45)

Q31. How do you click an element?
await page.getByRole('button', { name: 'Submit' }).click()

Q32. Difference between fill() and type()?
fill() sets the value directly and is fast; type() (deprecated in favor of pressSequentially) simulates keystrokes.

Q33. How do you handle a dropdown?
await page.getByLabel('Country').selectOption('IN')

Q34. How do you check a checkbox?
await page.getByLabel('Terms').check()

Q35. How do you hover?
await page.getByText('Menu').hover()

Q36. How do you drag and drop?
await page.locator('#src').dragTo(page.locator('#dst'))

Q37. How do you upload a file?
await page.getByLabel('Upload').setInputFiles('path/to/file.pdf')

Q38. How do you handle a native dialog?

typescript

page.on('dialog', async d => { await d.accept(); });

Q39. Difference between expect(locator).toBeVisible() and locator.isVisible()?
expect retries until timeout; isVisible() returns immediately and does not wait.

Q40. What is web-first assertion?
An assertion that retries automatically, e.g. await expect(locator).toHaveText('Done').

Q41. How do you assert a URL?
await expect(page).toHaveURL(/dashboard/)

Q42. How do you assert a title?
await expect(page).toHaveTitle('Home')

Q43. How do you assert an attribute?
await expect(locator).toHaveAttribute('disabled', '')

Q44. How do you assert element count?
await expect(locator).toHaveCount(3)

Q45. What is expect.soft()?
A soft assertion that records failure but continues the test instead of aborting.


Section 4: Waits & Timing (Q46–Q55)

Q46. Why is page.waitForTimeout() discouraged?
It introduces fixed delays, making tests slow and flaky. Prefer web-first assertions or waitFor.

Q47. How do you wait for a network response?
await page.waitForResponse(r => r.url().includes('/api/users') && r.status() === 200)

Q48. How do you wait for navigation?
await page.waitForURL('**/dashboard')

Q49. How do you wait for load state?
await page.waitForLoadState('networkidle')

Q50. What are the load states?
load, domcontentloaded, and networkidle.

Q51. What is a floating promise?
An async call without await, causing the next line to run before the action completes — the top cause of flakiness.

typescript

page.getByRole('button').click();          // ❌ floating
await page.getByRole('button').click();    // ✅

Q52. How do you set a custom timeout for one action?
await locator.click({ timeout: 10000 })

Q53. How do you override the global timeout?
Set timeout in playwright.config.ts, or pass --timeout on the CLI.

Q54. What is the default test timeout?
30 seconds.

Q55. What is the default assertion timeout?
5 seconds, configurable via expect.timeout.


Section 5: Network & API Testing (Q56–Q65)

Q56. How do you intercept a request?

typescript

await page.route('**/api/users', route => route.fulfill({ status: 200, body: '[]' }));

Q57. How do you abort a request?
await page.route('**/*.png', route => route.abort())

Q58. How do you modify a response?
Fetch the original with route.fetch(), modify the body, then route.fulfill().

Q59. Difference between page.request and request fixture?
page.request shares the page’s cookie jar (authenticated as the user); the request fixture has its own isolated cookie jar.

Q60. How do you mock an API response?
await page.route('**/api/login', r => r.fulfill({ json: { token: 'abc' } }))

Q61. How do you wait for multiple responses?
await Promise.all([page.waitForResponse(...), page.click(...)])

Q62. How do you test a REST API directly?
const res = await request.get('/users'); expect(res.ok()).toBeTruthy();

Q63. Can Playwright test GraphQL?
Yes, via request.post() with a JSON query body, or by intercepting the endpoint.

Q64. How do you record HAR?
await page.routeFromHAR('trace.har', { update: true }) or via config recordHar.

Q65. How do you replay a HAR?
await page.routeFromHAR('trace.har')


Section 6: Authentication & State (Q66–Q72)

Q66. How do you reuse authentication across tests?
Save storageState once during setup, then reference it via test.use({ storageState: 'auth.json' }).

Q67. How do you save storage state?
await context.storageState({ path: 'auth.json' })

Q68. What does storageState contain?
Cookies and localStorage origins.

Q69. How do you run a setup project?
Define a project named setup in config and add dependencies: ['setup'] to test projects.

Q70. How do you log in via API instead of UI?
Use the request fixture to POST credentials, then save the resulting storage state.

Q71. How do you test multiple users in parallel?
Create separate contexts with different storage states.

Q72. How do you clear cookies mid-test?
await context.clearCookies()


Section 7: Fixtures & Test Structure (Q73–Q82)

Q73. What is a fixture?
A reusable setup/teardown unit injected into tests, replacing beforeEach boilerplate.

Q74. How do you define a custom fixture?

typescript

export const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
});

Q75. Difference between test and test.extend?
test is the base runner; test.extend creates a customized runner with additional fixtures.

Q76. What are worker-scoped fixtures?
Fixtures defined with { scope: 'worker' } that are created once per worker rather than per test.

Q77. How do you override a fixture in one file?
test.use({ storageState: 'admin.json' }) at the top of the file.

Q78. What does beforeAll do?
Runs once before all tests in a file.

Q79. What does beforeEach do?
Runs before every test in a file.

Q80. What is test.describe?
Groups related tests and can apply hooks or test.use at the group level.

Q81. What is test.step?
A labeled sub-section inside a test that appears in reports, improving readability.

Q82. How do you skip a test?
test.skip(), test.skip(condition), or test.fixme().


Section 8: Page Object Model & TypeScript (Q83–Q90)

Q83. What is the Page Object Model?
A design pattern where each page is represented by a class encapsulating locators and actions.

Q84. How do you type a page object in TypeScript?

typescript

export class LoginPage {
  constructor(private readonly page: Page) {}
  readonly username = this.page.getByLabel('Username');
  async login(u: string, p: string): Promise<DashboardPage> {
    await this.username.fill(u);
    await this.page.getByLabel('Password').fill(p);
    await this.page.getByRole('button', { name: 'Sign in' }).click();
    return new DashboardPage(this.page);
  }
}

Q85. Why should navigation methods return the next page object?
It enforces flow correctness at compile time and chains steps fluently.

Q86. How do you share page objects across tests?
Expose them as fixtures in a custom test.extend.

Q87. What is as const used for in Playwright tests?
Freezing literal types, e.g. role arrays or config maps, so TypeScript narrows them correctly.

Q88. How do you type a locator array?
const rows: Locator[] = await page.getByRole('row').all();

Q89. How do you type a network response body?
const data = await res.json() as UserDto; or use a Zod schema for runtime validation.

Q90. Should page objects contain assertions?
Prefer keeping assertions in tests; page objects should expose state and actions. Some teams allow lightweight assertions in page objects for reuse.


Section 9: Debugging & Reporting (Q91–Q95)

Q91. How do you debug a failing test?
Run with --debug (Playwright Inspector), --headed, or use page.pause().

Q92. What is the Trace Viewer?
A tool that replays a test with DOM snapshots, network, console, and source — enabled via trace: 'on-first-retry'.

Q93. How do you enable tracing?
In config: use: { trace: 'on' } or per context: context.tracing.start({ screenshots: true, snapshots: true }).

Q94. How do you view a trace?
npx playwright show-trace trace.zip

Q95. What reporters ship with Playwright?
list, line, dot, html, json, junit, and blob.


Section 10: CI/CD & Best Practices (Q96–Q100)

Q96. How do you run Playwright in CI?
Install browsers with npx playwright install --with-deps, then run npx playwright test. Use the official Docker image or GitHub Action.

Q97. How do you parallelize tests?
Playwright parallelizes by file by default. Use fullyParallel: true for test-level parallelism and configure workers.

Q98. How do you handle retries?
Set retries: 2 in config or --retries=2. Combine with trace: 'on-first-retry' for diagnosis.

Q99. How do you shard tests across CI machines?
npx playwright test --shard=1/4

Q100. What are the top best practices for Playwright + TypeScript?

  1. Always await async calls (avoid floating promises).
  2. Prefer role/test-id locators over CSS/XPath.
  3. Use web-first assertions instead of manual waits.
  4. Isolate tests with contexts and storage state.
  5. Encapsulate pages in typed page objects.
  6. Enable traces on retry.
  7. Keep tests independent and parallel-safe.
  8. Validate API responses with schemas (e.g., Zod).
  9. Run in CI with sharding and retries.
  10. Treat flakiness as a bug, not noise.

100 Advanced Playwright + TypeScript Interview Questions and Answers

Below is a full set of 100 advanced interview questions with answers, assuming you have already mastered the fundamentals covered in the previous set. Each answer is written to be interview-ready and focuses on production-grade patterns, edge cases, and architectural decisions.


Section 1: Advanced Locators & Auto-Waiting (Q1–Q10)

Q1. How does Playwright’s auto-waiting actually work under the hood?
Playwright injects an actionability check before every interaction. The element must pass five conditions: attached to DOM, visible, stable (not animating), enabled, and receiving events (not obscured). Each check retries until timeout. This is implemented via injected scripts that poll the element state and a set of heuristics comparing bounding boxes across animation frames.

Q2. What causes “element is not stable” errors and how do you fix them?
The element’s bounding box changed between two consecutive animation frames, typically due to CSS animations, transitions, or layout shifts. Fixes: disable animations in config (animations: 'disabled'), wait for a stable parent, or target a non-animated ancestor.

Q3. When does getByRole fail to find an element that exists?
When the element lacks an implicit or explicit ARIA role, or when its accessible name is computed from hidden content. Also, custom components that render interactive elements without proper roles (e.g., a <div> styled as a button without role="button") won’t be found.

Q4. How do you handle a locator that matches multiple elements intentionally?
Use .filter(), .nth(), or iterate with .all(). For lists, prefer scoping: page.getByRole('row').filter({ hasText: 'Alice' }).getByRole('button'). Never rely on .first() in production tests; it’s a flakiness signal.

Q5. What is the difference between locator.waitFor({ state: 'attached' }) and locator.waitFor({ state: 'visible' })?
attached means the element exists in the DOM even if hidden. visible means it has a non-empty bounding box and is not display: none or visibility: hidden. For interaction, you almost always want visible.

Q6. How do you locate an element by its text that is split across child elements?
getByText normalizes whitespace and matches against the element’s full text content, so <span>Hello <b>World</b></span> is matched by getByText('Hello World'). For partial matches, pass { exact: false } (default) or use a regex.

Q7. What is a “strict mode violation” and when is it desirable?
Playwright throws when a locator resolves to more than one element during an action. This is desirable because it forces you to write specific locators. To opt out for intentional multi-element operations, use .all(), .nth(), or .first() with explicit intent.

Q8. How do you handle elements inside closed shadow DOM?
You cannot pierce closed shadow DOM from the page context. Options: expose a test hook on the component, use evaluate to access the shadow root if the host exposes it, or switch to component testing where you mount the component directly.

Q9. What is the “receives events” check and when does it cause false negatives?
Playwright checks that the element at the click point is the target or a descendant. It fails when an overlay, tooltip, or invisible element intercepts the click. Fix: dismiss the overlay, use force: true (last resort), or click the actual receiving element.

Q10. How do you write a locator that survives a component library upgrade?
Avoid internal class names, data attributes not part of the public contract, and deeply nested selectors. Prefer role-based locators, accessible names, and data-testid attributes that the team commits to maintaining.


Section 2: Network Interception & API Testing (Q11–Q25)

Q11. What is the difference between route.fulfill(), route.continue(), and route.fetch()?
fulfill() returns a mock response without hitting the server. continue() lets the request proceed, optionally with modified headers, method, or body. fetch() sends the request to the real server and returns the response so you can modify it before fulfilling .

Q12. How do you modify a response body while preserving the original headers and status?
Use route.fetch(), parse the response, modify the data, then route.fulfill({ response, body: JSON.stringify(modified) }). Passing the original response object preserves headers and status .

Q13. What is the page.request vs request fixture distinction in advanced scenarios?
page.request shares the page’s cookie jar and is authenticated as the current user. The request fixture has its own isolated cookie jar, making it suitable for admin operations, setup/teardown, or unauthenticated API checks .

Q14. How do you record and replay network traffic with HAR files?
Record with routeFromHAR(path, { update: true }) during development. Replay with routeFromHAR(path) in tests. HAR files capture request/response headers, bodies, and timings, providing realistic test data without hitting live services .

Q15. How do you simulate network throttling in Playwright?
Use CDP: client.send('Network.emulateNetworkConditions', { offline: false, downloadThroughput: speed * 1024 / 8, uploadThroughput: speed * 1024 / 8, latency: ms }). This lets you test loading states, timeouts, and retry logic .

Q16. What is the “Cookie header override” caveat with route.continue()?
You cannot override the Cookie header via route.continue({ headers }). The browser fills cookies from its own cookie jar. To change cookie state, use context.addCookies() or modify storageState .

Q17. How do you test a WebSocket connection in Playwright?
Playwright doesn’t have native WebSocket mocking, but you can: (a) evaluate window.ws.readyState in the page context , (b) use MSW (Mock Service Worker) to intercept WebSocket handlers , or (c) expose the socket on window and assert its state.

Q18. How do you validate API response schemas in Playwright tests?
Use Zod, AJV, or the playwright-schema-validator plugin. Fetch the response, then run schema.parse(await response.json()). This catches contract violations that status code checks miss .

Q19. How do you handle OAuth/SSO flows without driving the IdP UI every time?
Best practice: perform token exchange via API in a setup project, inject tokens into localStorage via addInitScript, navigate to the app, then save storageState. Keep one slow, real SSO test tagged @auth-integration for nightly runs .

Q20. How do you test retry logic with Playwright’s route mocking?
Set up a counter in the route handler. Return 503 for the first N requests, then 200. Assert the UI eventually shows the success state and that the counter equals the expected retry count .

Q21. What is the maxRedirects option in route.fetch() and when would you use it?
It controls how many redirects route.fetch() follows before returning the response. Useful for auth flows that redirect through multiple endpoints; you can assert on the final response or simulate a redirect loop failure.

Q22. How do you abort requests selectively by resource type?
Use route.request().resourceType() inside the handler: if (['image', 'font'].includes(type)) route.abort(); else route.continue();. This speeds up tests and lets you test offline/blocked-resource behavior .

Q23. What is route.fallback() and when do you need it?
fallback() continues to the next matching route handler instead of fulfilling immediately. Use it when you have multiple route handlers for the same URL and want conditional logic layered across them.

Q24. How do you test that your app handles a 500 error gracefully?
Intercept the API route, call route.fetch() to get the real response (or skip it), then route.fulfill({ status: 500, body: ... }). Assert the error UI appears .

Q25. Can you intercept requests made by a service worker?
By default, service workers bypass Playwright’s route interception. Set serviceWorkers: 'block' in context options to force all requests through the page’s network stack, making them interceptable .


Section 3: Advanced Fixtures & Test Architecture (Q26–Q40)

Q26. What is the difference between test.extend and mergeTests?
test.extend creates a new test object with additional fixtures, typically in one file. mergeTests combines multiple extended test objects into one, useful for composing fixtures from different domains (API, UI, auth) .

Q27. How do you create a fixture that depends on another fixture?
Declare the dependency in the fixture function’s argument destructuring: myFixture: async ({ page, request }, use) => { ... }. Playwright resolves dependencies automatically.

Q28. What is the “automatic fixture” pattern and when is it useful?
An automatic fixture runs even if not explicitly requested, via test.extend({ autoFixture: [async ({}, use) => { ... }, { auto: true }] }). Useful for global setup like console error monitoring or network logging.

Q29. How do you scope a fixture to run once per worker instead of per test?
Use { scope: 'worker' } in the fixture options. The fixture is initialized when the worker starts and torn down when it exits, ideal for expensive resources like database connections.

Q30. What is the “boxed fixture” pattern for safe teardown?
Wrap the value in an object with a dispose method so teardown always runs even if the test fails mid-use. Playwright 1.50+ supports explicit resource management with await using.

Q31. How do you share authentication state across parallel workers?
Create the auth state file in a setup project, then reference it via test.use({ storageState: 'auth/user.json' }). All workers read the same file, avoiding redundant login flows .

Q32. What is a “fixture override” and when should you avoid it?
Overriding a built-in fixture (like page) changes behavior for all tests in scope. Avoid it unless you need global instrumentation; prefer creating a new fixture that wraps the original.

Q33. How do you pass parameters to fixtures from test files?
Use a test.use({ myOption: value }) at the file or describe level, and declare the option as a fixture that reads the value. This is the “option fixture” pattern.

Q34. What is the difference between test.describe.serial and test.describe.parallel?
serial runs tests in order and stops on first failure, useful for dependent flows. parallel is the default and runs tests concurrently within the describe block.

Q35. How do you create a fixture that provides a typed page object?

typescript

export const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
});

This gives type-safe access to loginPage in every test .

Q36. What is the “fixture composition” pattern for cross-cutting concerns?
Layer fixtures: base.extend(authFixtures).extend(apiFixtures).extend(loggingFixtures). Each layer adds one concern, keeping fixtures focused and testable.

Q37. How do you clean up test data in a worker-scoped fixture?
Create a unique namespace per worker (e.g., worker-${workerInfo.workerIndex}), seed data in the fixture setup, and delete all data matching that namespace in teardown.

Q38. What is the “test data factory” pattern and how does it integrate with fixtures?
A factory is a function that creates entities via API with unique names. Expose it as a fixture that also tracks created entities and cleans them up in teardown.

Q39. How do you handle flaky tests at the framework level?
Set retries: 2 in config, trace: 'on-first-retry' for diagnosis, and use test.fail() to mark known flaky tests. Quarantine persistent flakes in a separate project and fix root causes rather than relying on retries.

Q40. What is test.step and how does it improve debuggability?
test.step('Login as admin', async () => { ... }) creates labeled sections in the trace viewer and reports. Steps can be nested and are useful for documenting multi-phase tests.


Section 4: Component Testing (Q41–Q50)

Q41. What is Playwright component testing and how does it differ from E2E?
Component testing mounts individual UI components in a real browser without the full application. It tests rendering, props, and interactions in isolation, running faster than E2E .

Q42. Which frameworks does Playwright component testing support?
React (@playwright/experimental-ct-react), Vue, and Svelte. It requires a separate config (playwright-ct.config.ts) and uses the mount fixture .

Q43. What is the “story” concept in Playwright’s newer component testing model?
A story is a wrapper that embeds a component in a specific scenario (props, providers, mock data). Stories live in *.story.tsx files and are rendered by a “gallery” page .

Q44. How does the mount fixture work?
const component = await mount(<App />) renders the component in the gallery and returns a Locator scoped to the component root. You can then use component.getByRole(...) for interactions .

Q45. How do you test a component that makes API calls?
Use route interception in the component test just like E2E: page.route('**/api/data', ...). Alternatively, pass mock data via props or a provider wrapper.

Q46. Can you use Playwright’s trace viewer with component tests?
Yes. Enable trace: 'on-first-retry' in playwright-ct.config.ts. The trace shows component interactions, DOM snapshots, and network calls .

Q47. What is the “gallery” in Playwright component testing?
A single page served by your dev server that exposes window.mount() and window.unmount(). It discovers *.story.* files and renders the selected story into a root element .

Q48. How do you test component state changes (e.g., toggles, accordions)?
Mount the component, assert initial state via toContainText or toHaveAttribute, perform the interaction, then assert the new state. The trace captures each step for debugging .

Q49. What are the limitations of Playwright component testing?
It’s experimental; the API may change. It doesn’t test routing, full-page layout, or backend integration. You still need E2E tests for those concerns .

Q50. When would you choose component testing over E2E?
For design system components, complex form controls, and UI states that are hard to trigger in a full app (e.g., error states, loading skeletons). Component tests run faster and are less flaky.


Section 5: Visual Regression & Accessibility (Q51–Q60)

Q51. How does toHaveScreenshot() work?
On first run, it captures a baseline PNG. On subsequent runs, it compares the current screenshot pixel-by-pixel against the baseline within configured tolerances .

Q52. What are the key config options for reducing false positives in visual tests?
animations: 'disabled' freezes CSS animations, caret: 'hide' hides text cursors, scale: 'css' normalizes DPI differences, and maxDiffPixelRatio: 0.01 allows 1% pixel difference .

Q53. How do you mask dynamic content in a screenshot?
Pass a mask array of locators: toHaveScreenshot({ mask: [page.locator('.timestamp')] }). The masked areas are rendered as solid rectangles in both baseline and comparison .

Q54. How do you update visual baselines when the UI changes intentionally?
Run npx playwright test --update-snapshots and commit the new PNGs. Review the diff before committing to ensure changes are intentional .

Q55. What is the “stability gate” pattern in visual testing?
Before taking a screenshot, assert that a critical element is visible (e.g., a heading or data row). This ensures the page has fully rendered and prevents capturing a blank or mid-render state .

Q56. How do you test accessibility with Playwright?
Use @axe-core/playwright: const results = await new AxeBuilder({ page }).analyze(); expect(results.violations).toEqual([]);. Run it as a separate test or integrate into existing tests.

Q57. What are the limitations of visual regression testing?
It catches pixel changes but not semantic regressions (e.g., a button that looks right but has wrong behavior). It can be flaky across OS/browser versions. It doesn’t test responsiveness unless you run multiple viewports.

Q58. How do you handle visual tests across different browsers?
Playwright generates separate baselines per browser and platform by default (e.g., screenshot-chromium-darwin.png). Commit all variants or run visual tests on a single canonical browser in CI.

Q59. What is toMatchSnapshot() vs toHaveScreenshot()?
toMatchSnapshot() compares arbitrary data (strings, objects). toHaveScreenshot() compares rendered page images. Use the former for DOM snapshots or API responses.

Q60. How do you test responsive design with Playwright?
Create projects with different viewport sizes (devices['iPhone 13'], devices['Desktop Chrome']), or use page.setViewportSize() within a test. Assert layout changes via element visibility or position.


Section 6: Debugging & Diagnostics (Q61–Q70)

Q61. What is the Trace Viewer and what does it capture?
A timeline of every action, DOM snapshot before/after each step, network requests, console logs, and source code. Enable via trace: 'on-first-retry' and view with npx playwright show-trace trace.zip .

Q62. How do you use page.pause() effectively?
It opens the Playwright Inspector and pauses execution, letting you step through actions, inspect locators, and resume. Use it for interactive debugging, not in committed tests.

Q63. What is the “floating promise” pattern and how do you detect it?
Calling an async method without await: page.click('button') instead of await page.click('button'). ESLint rules (@typescript-eslint/no-floating-promises) and Playwright’s own warnings help detect it.

Q64. How do you debug a test that fails only in CI?
Enable trace: 'on-first-retry', run with --workers=1 to rule out concurrency, check for timezone/locale differences, and compare CI viewport to local. The trace viewer is the primary tool.

Q65. What does the “Call log” in the trace viewer show?
A chronological list of Playwright API calls with their parameters, return values, and timing. It helps identify which action hung or returned unexpected data.

Q66. How do you capture console errors during a test?

typescript

const errors: string[] = [];
page.on('console', msg => {
  if (msg.type() === 'error') errors.push(msg.text());
});
// after test:
expect(errors).toEqual([]);

Q67. What is the “Snapshot” tab in the trace viewer?
A DOM snapshot of the page at the selected point in the timeline. You can inspect elements, see computed styles, and verify what the page looked like when an action ran.

Q68. How do you debug locator resolution?
Use page.locator('...').highlight() to visually highlight matched elements, or inspect the locator in the Playwright Inspector. The trace viewer shows which element each action targeted.

Q69. What is the “Network” tab in the trace viewer?
A list of all network requests with headers, bodies, timings, and status codes. Useful for diagnosing API failures, slow responses, or unexpected requests.

Q70. How do you record a video of a test run?
Set video: 'retain-on-failure' or 'on' in config. Videos are attached to the test result and viewable in the HTML report.


Section 7: Performance & Advanced Configuration (Q71–Q80)

Q71. How do you parallelize tests across files vs within files?
By default, Playwright parallelizes by file. fullyParallel: true enables test-level parallelism within files, requiring tests to be independent .

Q72. What is sharding and how do you use it?
npx playwright test --shard=1/4 splits tests across 4 machines. Each shard runs a deterministic subset, enabling horizontal scaling in CI.

Q73. How do you configure multiple projects for different browsers and devices?
In playwright.config.ts, define a projects array with different use options: { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, etc. Tests run against all projects by default.

Q74. What is the webServer config option?
It starts a dev server before tests and shuts it down after. Configure with command, url, and reuseExistingServer: !process.env.CI .

Q75. How do you set per-project timeouts?
Add timeout to the project’s use object, or override at the test level with test.setTimeout(ms).

Q76. What is globalSetup vs a setup project?
globalSetup is a single function that runs once before all projects. A setup project is a full Playwright project that runs before dependent projects and can use fixtures, reporters, and traces.

Q77. How do you run tests only on changed files in CI?
Use --only-changed (Playwright 1.42+) or integrate with your CI’s change detection to pass specific file paths.

Q78. What is the blob reporter and when is it useful?
It produces a report.zip that can be merged from multiple shards into a single HTML report using npx playwright merge-reports.

Q79. How do you configure retries per project?
Add retries: 2 to the project’s use object. Different projects can have different retry policies (e.g., flaky visual tests get more retries).

Q80. What is expect.configure() and when would you use it?
It creates a customized expect with default options: const slowExpect = expect.configure({ timeout: 10000 }). Useful for slow-running assertions in specific test suites.


Section 8: CI/CD & Production Patterns (Q81–Q90)

Q81. How do you cache Playwright browsers in GitHub Actions?
Cache ~/.cache/ms-playwright keyed on the Playwright version from package.json. This avoids re-downloading browsers on every run.

Q82. How do you handle test data isolation in parallel CI runs?
Use unique prefixes per worker/run (e.g., run-${process.env.GITHUB_RUN_ID}-worker-${workerIndex}). Clean up in worker teardown.

Q83. What is the “smoke test” project pattern?
A subset of critical tests tagged @smoke that run on every PR. Full suite runs on merge to main or nightly.

Q84. How do you report test results to a dashboard like TestDino or Allure?
Add the reporter to playwright.config.ts and set the API token via environment variable. Results stream during the run .

Q85. How do you handle secrets in CI?
Use GitHub Actions secrets or your CI’s secret store. Never commit credentials. For auth state, generate it in a setup step and pass the file path via env var.

Q86. What is the “merge reports” workflow for sharded runs?
Each shard uploads a blob report. A final job downloads all blobs and runs npx playwright merge-reports --reporter=html ./blobs.

Q87. How do you prevent flaky tests from blocking merges?
Run flaky tests in a separate non-blocking job, quarantine persistent flakes, and use test.fixme() with a tracking issue. Retries buy time but don’t fix root causes.

Q88. How do you test against a staging environment with real data?
Use environment-specific configs (baseURL from env), seed test data via API in setup, and clean up in teardown. Never run destructive tests against staging data.

Q89. What is the “canary” pattern for test selection?
Run a small, fast subset of tests on every commit, a medium suite on PR, and the full suite nightly. This balances feedback speed with coverage.

Q90. How do you version and migrate visual baselines?
Store baselines in the repo, update them when UI changes are intentional, and review diffs in PR. Use a dedicated branch for baseline updates to avoid noise.


Section 9: TypeScript-Specific Patterns (Q91–Q100)

Q91. How do you type a page object that returns the next page?

typescript

async login(user: string, pass: string): Promise<DashboardPage> {
  // ... actions
  return new DashboardPage(this.page);
}

This creates a fluent, type-safe navigation chain .

Q92. What is the satisfies operator and how does it help Playwright tests?
const config = { retries: 2 } satisfies PlaywrightTestConfig validates the shape without widening the type. Useful for config objects where you want both validation and literal inference.

Q93. How do you type network response bodies safely?
Define a Zod schema and parse: const data = UserSchema.parse(await response.json()). This gives you a typed, validated object at runtime.

Q94. What is the Locator type and how do you store locators in a page object?
Locator is the return type of page.locator() and page.getByRole(). Store them as readonly class properties initialized in the constructor.

Q95. How do you type a custom fixture that wraps a page object?

typescript

type Fixtures = { loginPage: LoginPage };
export const test = base.extend<Fixtures>({ ... });

The generic parameter gives full type safety for test and test.use.

Q96. What is the Page vs Locator return type distinction?
Page represents a tab; Locator represents an element. Page objects typically take Page in the constructor and expose Locator properties.

Q97. How do you use discriminated unions for test data?

typescript

type User = { role: 'admin'; permissions: string[] } | { role: 'viewer' };

This forces exhaustive handling and prevents invalid combinations in test factories.

Q98. What is the as const pattern for locator arrays?
const roles = ['button', 'link'] as const preserves literal types, enabling roles.map(r => page.getByRole(r)) with proper narrowing.

Q99. How do you type page.evaluate() return values?
const title = await page.evaluate<string>(() => document.title) — pass the expected return type as a generic. For complex objects, define an interface and cast.

Q100. What is the “branded type” pattern for test IDs?

typescript

type TestId = string & { __brand: 'TestId' };

This prevents accidentally passing a raw string where a test ID is expected, catching mix-ups at compile time.


Summary of Advanced Topics Covered

SectionFocusKey Patterns
1Advanced LocatorsAuto-waiting internals, strict mode, shadow DOM
2Network & APIRoute interception, HAR, OAuth bypass, WebSockets
3FixturesComposition, worker scope, boxed teardown
4Component TestingMount fixture, stories, gallery
5Visual & A11yBaseline management, masking, axe-core
6DebuggingTrace viewer, floating promises, console capture
7PerformanceSharding, parallelization, webServer
8CI/CDCaching, secrets, smoke tests, merge reports
9TypeScriptPage object typing, Zod validation, branded types
← Previous · Lesson 87Assertions Interview QuestionsNext → · Lesson 89Interview Questions for Experienced (2, 3, 5+ Years)
View the full Playwright tutorial syllabus
Categories Uncategorized 1 Comment

Playwright BDD Framework Step By Step

by
Playwright Tutorial›Module 10: Framework Design
Lesson 67 of 9174% through the course

To build an automated test framework using Playwright, Cucumber (BDD), and the Page Object Model (POM) pattern with TypeScript, follow this structured setup.

Cucumber Framework Official docs

1.Initialize Project & Dependencies:Node.js required.

Create a project directory and install the necessary dependencies for Playwright, @cucumber/cucumber, and TypeScript.

Bash

mkdir playwright-cucumber-pom
cd playwright-cucumber-pom
npm init -y
npm install --save-dev @playwright/test @cucumber/cucumber ts-node typescript @types/node
npx playwright install

2.Configure TypeScript & Cucumber:Set up root configuration files.

Create a tsconfig.json file in your root folder:

JSON

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Create a cucumber.json file in your root folder to define execution profiles:

JSON

{
  "default": {
    "formatOptions": {
      "snippetInterface": "async-await"
    },
    "paths": ["src/tests/features/**/*.feature"],
    "require": ["src/tests/steps/**/*.ts", "src/hooks/hooks.ts"],
    "requireModule": ["ts-node/register"],
    "format": ["progress", "html:reports/cucumber-report.html"]
  }
}

3.Set Up Browser Hooks & Context Management:Manages browser lifecycle.

Create src/hooks/hooks.ts to manage launching and closing the browser instance before and after scenarios.

TypeScript

import { Before, After, BeforeAll, AfterAll } from '@cucumber/cucumber';
import { Browser, BrowserContext, Page, chromium } from '@playwright/test';

let browser: Browser;
let context: BrowserContext;
export let page: Page;

BeforeAll(async () => {
  browser = await chromium.launch({ headless: false });
});

Before(async () => {
  context = await browser.newContext();
  page = await context.newPage();
});

After(async () => {
  await page.close();
  await context.close();
});

AfterAll(async () => {
  await browser.close();
});

4.Create the Page Object Model (POM):Encapsulate page UI elements and actions.

Create src/pages/LoginPage.ts to store page selectors and interaction logic.

TypeScript

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

export class LoginPage {
  private page: Page;
  private usernameInput: Locator;
  private passwordInput: Locator;
  private loginButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.locator('#user-name');
    this.passwordInput = page.locator('#password');
    this.loginButton = page.locator('#login-button');
  }

  async navigateTo(url: string) {
    await this.page.goto(url);
  }

  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
}

5.Write the Feature File:Define BDD scenarios using Gherkin.

Create src/tests/features/login.feature:

Gherkin

Feature: User Authentication

  Scenario: Successful login with valid credentials
    Given I navigate to "https://www.saucedemo.com/"
    When I log in with username "standard_user" and password "secret_sauce"
    Then I should see the inventory page title "Swag Labs"

6.Implement Step Definitions:Map Feature steps to Page Object code.

Create src/tests/steps/loginSteps.ts:

TypeScript

import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { page } from '../../hooks/hooks';
import { LoginPage } from '../../pages/LoginPage';

let loginPage: LoginPage;

Given('I navigate to {string}', async (url: string) => {
  loginPage = new LoginPage(page);
  await loginPage.navigateTo(url);
});

When('I log in with username {string} and password {string}', async (username: string, password: string) => {
  await loginPage.login(username, password);
});

Then('I should see the inventory page title {string}', async (expectedTitle: string) => {
  const actualTitle = await page.title();
  expect(actualTitle).toBe(expectedTitle);
});

7.Add Execution Script & Run Tests:Execute tests via npm.

Update your package.json file to include a test command:

JSON

"scripts": {
  "test": "cucumber-js"
}

Execute your framework in the terminal:

Bash

npm test

Configure Reporting in Cucumber Framework

1. Install Reporter Package:

Bash

npm install --save-dev multiple-cucumber-html-reporter

2. Configure JSON Output in cucumber.json:

Ensure Cucumber outputs test results in JSON format so the reporter engine can process them:

JSON

{
  "default": {
    "requireModule": ["ts-node/register"],
    "require": ["src/hooks/**/*.ts", "src/tests/steps/**/*.ts"],
    "format": ["json:reports/cucumber_report.json"]
  }
}

3. Create Report Generation Generator Script (reporter.js):

Create reporter.js at the root of your project:

JavaScript

const report = require('multiple-cucumber-html-reporter');

report.generate({
  jsonDir: './reports/',
  reportPath: './reports/html-report/',
  metadata: {
    browser: { name: 'chromium', version: 'latest' },
    device: 'Local Test Engine',
    platform: { name: 'Windows/Linux' }
  },
  customData: {
    title: 'Execution Info',
    data: [
      { label: 'Project', value: 'Playwright Cucumber Framework' },
      { label: 'Environment', value: 'Staging' }
    ]
  }
});

3. Update your package.json to unify test execution, reporting, and linting into seamless npm commands:

JSON

"scripts": {
  "test": "cucumber-js || npm run report",
  "test:smoke": "cucumber-js --tags '@smoke' || npm run report",
  "test:parallel": "cucumber-js --parallel 4 || npm run report",
  "report": "node reporter.js"
}
← Previous · Lesson 66BDD IntegrationNext → · Lesson 68Real-World Enterprise Framework
View the full Playwright tutorial syllabus
Categories Uncategorized 2 Comments
Older posts
Page1 Page2 … Page13 Next →
  • API Testing Practice: Free REST API with Auth and 141 Scenarios
  • Dummy Banking Website
  • ISTQB Foundation Level Sample Paper 3 (CTFL v4.0, 40 Questions with Answers)
  • ISTQB Foundation Level Sample Paper 2 (CTFL v4.0, 40 Questions with Answers)
  • ISTQB Foundation Level Sample Paper 1 (CTFL v4.0, 40 Questions with Answers)

  • Automation Practice Page
  • Dummy Booking Website
  • Login Page
© 2026 SQA Tools • Built with GeneratePress