Playwright annotations help QA teams control test execution, organize large test suites, manage unstable tests, and improve CI/CD execution. In real-world automation projects, annotations are heavily used for:
Smoke testing
Regression filtering
Environment-specific execution
Handling known bugs
Managing flaky tests
Faster debugging
CI pipeline optimization
Playwright provides built-in annotations such as:
test.only()
test.skip()
test.fixme()
test.fail()
test.slow()
Tags like @smoke, @regression, @sanity
It also supports custom annotations and runtime annotations using testInfo.annotations.
test.skip(browserName === 'firefox', 'Known issue in Firefox');
await page.goto('https://example.com');
await page.click('#checkout');
await expect(page).toHaveURL(/checkout/);
});
});
21. Recommended Annotation Strategy for Real Projects
Small Projects
Use:
@smoke
@regression
Medium Projects
Use:
@smoke
@sanity
@regression
@critical
Enterprise Projects
Use:
Browser-specific annotations
Environment-based skipping
Jira-linked annotations
CI-based tagging
Runtime annotations
Module-based grouping
22. Final Recommended Framework Workflow
Daily Execution
npx playwright test --grep @smoke
Nightly Regression
npx playwright test --grep @regression
Release Validation
npx playwright test --grep @critical
23. Best Practices Summary
Recommended
✔ Use tags for filtering ✔ Use fail() for known bugs ✔ Use slow() for long tests ✔ Use fixme() for broken scenarios ✔ Use custom annotations for Jira tracking ✔ Organize tests with describe() ✔ Maintain naming conventions
Avoid
✘ Permanent skipped tests ✘ Random tag naming ✘ Pushing test.only() to CI ✘ Too many annotations on one test
Answer: AI in software testing refers to the use of Artificial Intelligence and Machine Learning techniques to improve testing activities such as test case generation, defect prediction, test maintenance, test optimization, visual validation, and intelligent automation.
AI helps QA teams:
Reduce repetitive manual work
Improve test coverage
Detect flaky tests
Predict high-risk areas
Generate intelligent test data
Speed up regression testing
2. What is the difference between Traditional Automation and AI-based Automation?
Traditional Automation
AI-based Automation
Rule-based
Learns from data
Hardcoded locators
Self-healing locators
Requires frequent maintenance
Adaptive maintenance
Static scripts
Intelligent execution
Breaks easily on UI changes
Handles dynamic changes
Example: In Playwright or Selenium, if an XPath changes, traditional automation fails. AI-based tools can identify elements using attributes, text, position, or visual recognition.
3. What are Self-Healing Test Scripts?
Answer: Self-healing scripts automatically recover from UI changes by identifying alternative locators when the original locator fails.
Example:
If:
id="loginBtn"
changes to:
id="signinBtn"
AI tools analyze:
Text
CSS structure
Neighbor elements
Historical locator data
and automatically update the locator.
4. Name Some AI Testing Tools You Have Worked With
AI is transforming software testing and QA engineering by improving test creation, maintenance, bug analysis, reporting, and CI/CD automation. Modern QA engineers now use AI tools for:
Automated test generation
Self-healing locators
Visual testing
API testing
Test data generation
Root cause analysis
Intelligent reporting
Agentic test execution
Code assistance
Here are the top AI tools every QA engineer should know in 2026.
In Playwright, fixtures are reusable setup and teardown utilities that help you share test data, page objects, authentication, API clients, and configurations across tests.
Custom fixtures make your framework:
Reusable
Maintainable
Scalable
Cleaner and easier to read
1. What is a Fixture in Playwright?
A fixture is a setup environment provided to your test before execution.
Playwright already provides built-in fixtures such as:
Exception handling in JavaScript is used to manage runtime errors without stopping the entire program. It helps developers write stable and reliable applications.
What is an Exception?
An exception is an unexpected error that occurs while the program is running.
Examples:
Dividing by invalid values
Accessing undefined variables
Invalid JSON parsing
Network/API failures
Custom validation errors
Why Exception Handling is Important
Exception handling helps to:
Prevent application crashes
Show meaningful error messages
Debug applications easily
Handle API/database failures gracefully
Improve user experience
1. The Core Structure: try...catch
The try block contains code that might throw an error. If an error occurs, JavaScript immediately stops executing the try block and jumps to the catch block.
JavaScript
try {
// Code that might fail
let result = 10 / unknownVariable;
console.log("This line will not run");
} catch (error) {
// Code to handle the error
console.log("An error occurred:", error.message);
}
console.log("Execution continues normally.");
2. Standard Error Properties
Inside the catch block, the error object provides valuable diagnostic metadata:
error.name: The type of error (e.g., ReferenceError, TypeError).
error.message: A human-readable description of what went wrong.
error.stack: The stack trace showing where the error occurred in the source files.
The optional finally block always executes, regardless of whether an error occurred or was caught. It is ideal for cleanup tasks (e.g., closing file streams, resetting UI states, or clearing timers).
You can trigger explicit errors using the throw statement. While you can throw primitives (strings or numbers), it is best practice to throw standard Error objects so you retain stack trace information.
JavaScript
function withdrawMoney(amount, balance) {
if (typeof amount !== 'number') {
throw new TypeError("Amount must be a number");
}
if (amount > balance) {
throw new Error("Insufficient funds for this withdrawal");
}
return balance - amount;
}
try {
let newBalance = withdrawMoney(150, 100);
} catch (err) {
console.log(`[${err.name}] ${err.message}`);
}
5. Built-in JavaScript Error Types
Understanding common built-in error classes helps you diagnose issues quickly:
Error Type
Trigger Cause
Example
ReferenceError
Accessing an undeclared variable
console.log(x);
TypeError
Performing an invalid operation on a value
"text".toUpperCase() works, but (123).toUpperCase() fails
SyntaxError
Invalid JavaScript syntax (usually caught at parse time)
JSON.parse("{ bad json }")
RangeError
A numeric variable or parameter is outside its valid range
new Array(-1)
6. Asynchronous Exception Handling
Traditional try...catch blocks cannot catch errors inside asynchronous callbacks or promises because the try block finishes executing before the async callback runs.
Promises
Use the .catch() method to handle rejected promises:
JavaScript
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("API server offline")), 1000);
});
}
fetchData()
.then(data => console.log(data))
.catch(err => console.log("Promise Error:", err.message));
async / await
With async / await, you can wrap asynchronous code in a standard try...catch block:
JavaScript
async function loadUserData() {
try {
let response = await fetchData(); // Waits for promise resolution/rejection
console.log(response);
} catch (err) {
console.log("Async/Await Error:", err.message);
}
}
loadUserData();
JavaScript provides standard built-in error constructors that inherit from the base Error object. Each error type corresponds to a specific category of runtime exception.
1. Core Built-In Error Types
Error (Generic Base Error)
The generic base object used for general exceptions or as a parent class for custom user-defined errors.
JavaScript
try {
throw new Error("Application failed to process the request.");
} catch (err) {
console.log(err.name); // "Error"
console.log(err.message); // "Application failed to process the request."
}
TypeError
Occurs when an operation is performed on a value of an unexpected data type, such as attempting to invoke a non-function or reading properties of null/undefined.
JavaScript
try {
const count = 42;
count.toUpperCase(); // TypeError: count.toUpperCase is not a function
} catch (err) {
console.log(err.name); // "TypeError"
}
ReferenceError
Occurs when trying to access or dereference a variable that does not exist or has not been initialized (such as accessing a let/const variable in its Temporal Dead Zone).
Occurs when code violates JavaScript language syntax rules. Parsing utilities like JSON.parse() or eval() throw runtime SyntaxErrors when passed malformed input.
Occurs when a numeric value or function argument falls outside its allowed range or bounds (e.g., invalid array lengths, out-of-bounds numbers in .toFixed(), or max stack size recursion).
Introduced in ES2021, AggregateError wraps multiple errors into a single error object. It is automatically thrown by Promise.any() when every promise passed to it rejects.
Introduced with Explicit Resource Management (using declarations). It occurs when an error is thrown during the cleanup/disposal of a resource while another error was already active.
JavaScript
try {
// Represents a primary error and a secondary disposal error
throw new SuppressedError(
new Error("Cleanup failed"), // The new error during disposal
new Error("Main logic failed"), // The original suppressed error
"Resource disposal failure"
);
} catch (err) {
console.log(err.error.message); // "Cleanup failed"
console.log(err.suppressed.message); // "Main logic failed"
}
EvalError
A legacy error object created for issues related to the global eval() function. While modern JS engines no longer throw native EvalErrors (favoring SyntaxError or TypeError instead), the constructor remains in the language specification for backwards compatibility.