Expert-level guides for modern QA professionals. Choose your path below.
Complete SDET/QA interview prep. Lessons, code examples, and model answers — built for working professionals.
Master Anthropic's agentic coding CLI — from slash commands to MCP servers, skills, hooks, and full IDE integration.
Your complete Playwright TypeScript interview roadmap
This guide covers everything a senior QA/SDET needs to ace a Playwright TypeScript interview. Each lesson builds on the last, ending with a full Q&A section.
Architecture and competitive advantages
Playwright is an open-source end-to-end testing framework by Microsoft that communicates with browsers via CDP/WebSocket — bypassing WebDriver entirely.
| Feature | Playwright | Selenium |
|---|---|---|
| Protocol | CDP / WebSocket | WebDriver (HTTP) |
| Auto-wait | ✓ Built-in | ✕ Manual |
| Multi-tab | ✓ Native | ⚠ Complex |
| Network mock | ✓ page.route() | ✕ External proxy |
| Trace viewer | ✓ Built-in | ✕ |
Scaffold a production-ready project
# Create new project npm init playwright@latest # Add to existing project npm install -D @playwright/test npx playwright install
import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', fullyParallel: true, retries: process.env.CI ? 2 : 0, reporter: 'html', use: { baseURL: 'https://example.com', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, { name: 'mobile', use: { ...devices['iPhone 12'] } }, ], });
Find elements reliably at scale
page.getByRole('button', {name:'Submit'}) — accessible & resilientpage.getByLabel('Email address') — form fieldspage.getByTestId('submit-btn') — requires data-testidpage.getByText('Sign in') — for static text// Chaining locators const form = page.getByRole('form', { name: 'Login' }); await form.getByLabel('Username').fill('rajib'); await form.getByRole('button', { name: 'Sign in' }).click(); // Filter locators await page.getByRole('listitem') .filter({ hasText: 'Product A' }) .getByRole('button', { name: 'Add to cart' }) .click();
Simulate every user interaction
// Basic actions await page.getByLabel('Email').fill('test@example.com'); await page.getByRole('button').click(); await page.getByRole('button').dblclick(); await page.getByText('Menu').hover(); // Keyboard await page.keyboard.press('Tab'); await page.keyboard.press('Control+a'); // File upload await page.getByLabel('Upload').setInputFiles('./data/test.pdf'); // Drag and drop await page.dragAndDrop('#source', '#target'); // Select dropdown await page.getByRole('combobox').selectOption('Germany'); // Checkbox await page.getByRole('checkbox').check();
Verify state with confidence
// Locator assertions (auto-retry up to timeout) await expect(page.getByRole('heading')).toBeVisible(); await expect(page.getByRole('button')).toBeEnabled(); await expect(page.getByTestId('status')).toHaveText('Success'); await expect(page.getByLabel('Name')).toHaveValue('Rajib'); // Page assertions await expect(page).toHaveTitle('Dashboard'); await expect(page).toHaveURL('https://app.example.com/home'); // Soft assertions — continue test even on failure await expect.soft(page.getByTestId('price')).toHaveText('29'); await expect.soft(page.getByTestId('stock')).toBeVisible(); // All soft assertion failures reported at end
Eliminate flakiness without sleeps
// Wait for element state await page.getByRole('progressbar').waitFor({ state: 'hidden' }); // Wait for network idle await page.waitForLoadState('networkidle'); // Wait for URL change await page.waitForURL('**/dashboard'); // Wait for response (synchronise click + network) const [response] = await Promise.all([ page.waitForResponse('**/api/orders'), page.getByRole('button', { name: 'Place Order' }).click() ]); // NEVER do this — arbitrary sleep = flaky // await page.waitForTimeout(3000);
Scalable test architecture with TypeScript
import { Page, Locator } from '@playwright/test'; export class LoginPage { readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitBtn: Locator; constructor(private readonly page: Page) { this.emailInput = page.getByLabel('Email'); this.passwordInput = page.getByLabel('Password'); this.submitBtn = page.getByRole('button', { name: 'Sign in' }); } async goto() { await this.page.goto('/login'); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitBtn.click(); } }
Dependency injection for test setup
import { test as base } from '@playwright/test'; import { LoginPage } from './LoginPage'; type MyFixtures = { loginPage: LoginPage; loggedInPage: LoginPage }; export const test = base.extend<MyFixtures>({ loginPage: async ({ page }, use) => { const lp = new LoginPage(page); await lp.goto(); await use(lp); }, loggedInPage: async ({ page }, use) => { const lp = new LoginPage(page); await lp.goto(); await lp.login('user@test.com', 'secret'); await use(lp); } });
REST API validation and UI+API hybrid patterns
import { test, expect } from '@playwright/test'; test('POST /users creates a user', async ({ request }) => { const response = await request.post('/api/users', { data: { name: 'Rajib', role: 'admin' } }); expect(response.status()).toBe(201); const body = await response.json(); expect(body.name).toBe('Rajib'); }); // Hybrid: API setup then UI verify test('order appears in UI after API creation', async ({ request, page }) => { const res = await request.post('/api/orders', { data: { product: 'Widget', qty: 1 } }); const { id } = await res.json(); await page.goto(`/orders/${id}`); await expect(page.getByText('Widget')).toBeVisible(); });
Find failures faster with built-in tooling
// Programmatic pause — opens Playwright Inspector await page.pause(); // Capture trace manually await context.tracing.start({ screenshots: true, snapshots: true }); // ... test actions await context.tracing.stop({ path: 'trace.zip' });
Control HTTP traffic in tests
// Mock an API response await page.route('**/api/products', route => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([{ id: 1, name: 'Widget' }]) }) ); // Abort image/font requests to speed up tests await page.route('**/*.{png,jpg,svg}', route => route.abort()); // Modify live response on the fly await page.route('**/api/user', async route => { const response = await route.fetch(); const json = await response.json(); json.role = 'superadmin'; await route.fulfill({ response, json }); });
Model answers for senior SDET interviews
Anthropic's agentic coding CLI explained
Claude Code is Anthropic's official command-line tool that gives Claude direct access to your local environment — files, terminal, git, and external tools. Unlike a chatbot, it reads your codebase, runs commands, and solves complex tasks autonomously.
Get Claude Code running in minutes
# Install globally npm install -g @anthropic-ai/claude-code # Set your API key export ANTHROPIC_API_KEY=sk-ant-... # Or use OAuth login claude login # Start in your project directory cd my-playwright-project claude
# Explain the project > Explain the structure of this Playwright project # Generate a test > Write a Playwright test for the login page at /login # Run and fix > Run the login tests and fix any failures
Built-in CLI commands for power users
| Command | What it does |
|---|---|
| /help | Show all available commands |
| /clear | Clear conversation history (free up context) |
| /compact | Summarise conversation to reduce token usage |
| /model | Switch Claude model (Sonnet / Opus) |
| /review | Request code review of recent changes |
| /init | Generate CLAUDE.md for current project |
| /permissions | View and manage tool permissions |
| /memory | View and edit persistent memory |
| /mcp | List and manage connected MCP servers |
| /cost | Show token usage and API cost for session |
Generate a Playwright TypeScript test for: $ARGUMENTS Requirements: - Use Page Object Model pattern - Include happy path and error scenarios - Add meaningful test descriptions - Use getByRole locators where possible
The three layers of reusability
| Concept | Location | Purpose | When to Use |
|---|---|---|---|
| Slash Command | .claude/commands/*.md | Prompt template, invoked by /name | Repeated prompts with parameters |
| Skill | /mnt/skills/ or .claude/skills/ | Reusable workflow with SKILL.md | Complex multi-step procedures |
| Sub-agent | Created at runtime | Parallel autonomous task execution | Long-running or parallelisable tasks |
A Skill is a folder containing a SKILL.md that describes a reusable, structured workflow. Claude reads SKILL.md before executing — it encodes best practices, constraints, and step-by-step guidance.
# Playwright Test Generation Skill ## Steps 1. Read existing tests in /tests to match patterns 2. Identify page objects in /pages directory 3. Generate test using existing POM classes 4. Place in correct directory per naming convention 5. Run test and fix any immediate failures ## Constraints - Always use getByRole as primary locator - Never use waitForTimeout - Tests must be idempotent
Intercept Claude's actions with shell scripts
| Hook Type | When it fires | Use case |
|---|---|---|
| PreToolUse | Before Claude uses any tool | Validate, log, gate dangerous commands |
| PostToolUse | After Claude uses any tool | Run linter, auto-format, log results |
| Stop | When Claude finishes a task | Send Slack notification, run tests |
| Notification | When Claude sends a message | Desktop alerts for long tasks |
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "npx eslint --fix $CLAUDE_FILE_PATHS"
}]
}],
"Stop": [{
"hooks": [{
"type": "command",
"command": "osascript -e 'display notification \"Claude finished\"'"
}]
}]
}
}
Connect Claude to external tools and browsers
MCP (Model Context Protocol) is an open standard that lets Claude connect to external services. For QA, the Playwright MCP server lets Claude control a real browser.
# Install Playwright MCP server npm install -g @playwright/mcp # Connect in Claude Code claude mcp add playwright -- npx @playwright/mcp@latest # Verify connection /mcp
> Navigate to https://myapp.com/login and screenshot > Click Sign In and verify we reach /dashboard > Fill the registration form and generate a test from what you did
Build reusable workflows for your team
.claude/
skills/
pw-test-gen/
SKILL.md ← Required: workflow definition
examples/ ← Optional: sample inputs/outputs
templates/ ← Optional: code templates
# [Skill Name] ## Purpose One sentence describing what this skill does. ## Pre-conditions - Project uses Playwright TypeScript - POM files exist in /pages ## Steps 1. Read SKILL.md context 2. Scan existing patterns in /tests 3. Identify relevant page objects 4. Generate test file 5. Run and validate ## Constraints - Naming: *.spec.ts - Never hard-code test data - Always clean up created data
What Claude Code can do and how to control it
| Tool | What it does | Risk |
|---|---|---|
| Read | Read file contents | Low |
| Write | Create / overwrite files | Medium |
| Edit | Targeted string replacement | Medium |
| Bash | Execute shell commands | High |
| WebSearch | Search the internet | Low |
| MCP tools | External service actions | Varies |
# Restrict to safe tools only claude --allowedTools Read,Write,Edit # Deny specific tools claude --deniedTools Bash # Non-interactive CI mode claude --print "Run all tests" \ --allowedTools Read,Bash \ --output-format json
Give Claude persistent project context
| Type | Scope | Persists? |
|---|---|---|
| CLAUDE.md (repo root) | Project-wide | Yes — read every session |
| CLAUDE.md (subdirectory) | That folder only | Yes — read when in that dir |
| /memory command | User-level | Yes — across all projects |
| Conversation context | Current session | No — lost on exit |
# Project Context ## Tech Stack - Playwright TypeScript (v1.43+) - React 18 frontend ## Test Conventions - All tests in /tests, named *.spec.ts - Page objects in /pages/*.page.ts - Use getByRole as primary locator ## Commands - npm test — run all tests - npm run test:ui — Playwright UI mode ## DO NOT - Use waitForTimeout - Hard-code environment URLs - Commit credentials to repo
Tune Claude Code for your workflow
{
"model": "claude-sonnet-4-5",
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npx playwright*)",
"Read(**)",
"Write(tests/**)"
],
"deny": [
"Bash(rm -rf*)",
"Bash(git push*)"
]
},
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
Claude Code alongside VS Code, Cursor, and JetBrains
Use Cursor for inline completions and quick edits; use Claude Code for larger autonomous tasks like "refactor all tests to use POM" or "add fixtures to every spec file".
> I'm looking at tests/login.spec.ts — refactor to use LoginPage POM > The file I just edited has a TypeScript error, fix it > Generate tests for every exported function in src/utils/validator.ts
Interview-quality Claude Code CLI questions