🕒 Teaching Flow
1. Story Intro (5 min)
👉 "Imagine iframes are like picture frames hanging on a wall - each frame shows a different website inside your main website. Just like you need to step closer to read what's in a specific frame, Playwright needs special instructions to interact with iframe content."
Real-World Analogy:
Iframe = Picture frame showing another website
Config Options = Settings panel for your test environment
Parallel Running = Multiple workers doing different tasks simultaneously
Iframe = Picture frame showing another website
Config Options = Settings panel for your test environment
Parallel Running = Multiple workers doing different tasks simultaneously
Part 1: Iframe Handling (40 min)
What is an Iframe? (Simple Explanation)
Iframe (Inline Frame) is like a window inside a window. It's a way to embed another website or content inside your current webpage.
Think of it this way:
Example: YouTube videos embedded in websites, Google Maps, payment forms, chat widgets
Think of it this way:
- 🏠 Main Website = Your house
- 🖼️ Iframe = A TV screen showing another channel
- 📺 Iframe Content = The show playing on that TV
Example: YouTube videos embedded in websites, Google Maps, payment forms, chat widgets
Why Iframes are Tricky for Testing
| Regular Elements | Iframe Elements |
|---|---|
| ✅ Direct access | ❌ Need special navigation |
| 🎯 Simple locators | 🔍 Complex locator paths |
| ⚡ Fast interaction | ⏳ Slower (context switching) |
| 🔄 Easy debugging | 🐛 Harder to debug |
Step 1: Basic Iframe Detection
📂 tests/iframe-basics.spec.js
import { test, expect } from '@playwright/test';
test.describe('Iframe Basics', () => {
test('Detect iframe on page', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/iframe');
// Method 1: Count total iframes
const iframeCount = await page.locator('iframe').count();
console.log(`📊 Total iframes found: ${iframeCount}`);
// Method 2: Check if specific iframe exists
const iframeExists = await page.locator('iframe#mce_0_ifr').isVisible();
expect(iframeExists).toBeTruthy();
// Method 3: Get iframe attributes
const iframeSrc = await page.locator('iframe').getAttribute('src');
console.log('🔗 Iframe source:', iframeSrc);
});
});
Step 2: Accessing Iframe Content
Method 1: Using frameLocator() (Recommended)
test('Access iframe content with frameLocator', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/iframe');
// Step 1: Get the iframe using frameLocator
const iframe = page.frameLocator('iframe#mce_0_ifr');
// Step 2: Interact with elements inside iframe
await iframe.locator('body').click();
await iframe.locator('body').fill('Hello from Playwright!');
// Step 3: Verify content
const content = await iframe.locator('body').textContent();
expect(content).toContain('Hello from Playwright!');
console.log('✅ Successfully interacted with iframe content');
});
Step 3: Multiple Iframe Handling
Handling Multiple Iframes
test('Handle multiple iframes', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/nested_frames');
// Method 1: Access nested iframes
const topFrame = page.frameLocator('frame[name="frame-top"]');
const middleFrame = topFrame.frameLocator('frame[name="frame-middle"]');
// Get text from nested iframe
const middleText = await middleFrame.locator('body').textContent();
expect(middleText).toContain('MIDDLE');
console.log('📝 Middle frame text:', middleText);
// Method 2: Access by index
const firstFrame = page.frameLocator('frame').first();
const firstFrameText = await firstFrame.locator('body').textContent();
console.log('📝 First frame text:', firstFrameText);
// Method 3: Access by name attribute
const leftFrame = page.frameLocator('frame[name="frame-left"]');
const leftFrameText = await leftFrame.locator('body').textContent();
expect(leftFrameText).toContain('LEFT');
console.log('📝 Left frame text:', leftFrameText);
});
Step 4: Real-World Iframe Examples
YouTube Video Iframe
test('Interact with YouTube iframe', async ({ page }) => {
// Create a simple HTML page with YouTube iframe
await page.setContent(`
My Website
`);
// Wait for iframe to load
await page.waitForSelector('iframe#youtube-player');
// Access iframe content
const youtubeFrame = page.frameLocator('iframe#youtube-player');
// Check if play button exists (YouTube iframe)
const playButton = youtubeFrame.locator('.ytp-play-button');
await expect(playButton).toBeVisible();
console.log('✅ YouTube iframe loaded successfully');
});
Step 5: Google Maps Iframe
Google Maps Integration
test('Interact with Google Maps iframe', async ({ page }) => {
await page.setContent(`
Location Finder
`);
// Wait for maps to load
await page.waitForSelector('iframe#google-maps');
const mapsFrame = page.frameLocator('iframe#google-maps');
// Wait for map content to be ready
await mapsFrame.locator('[role="main"]').waitFor({ state: 'visible' });
console.log('✅ Google Maps iframe loaded successfully');
});
Part 2: Configuration Options (50 min)
What are Configuration Options? (Simple Explanation)
Configuration Options are like the settings panel for your test environment. They tell Playwright how to behave, what devices to use, and how to run your tests.
Think of it this way:
Think of it this way:
- 🎮 Config File = Game settings menu
- 📱 Devices = Different gaming consoles
- 🖥️ Viewport = Screen size and resolution
- 👥 Workers = Number of players playing simultaneously
Step 1: Basic playwright.config.js Setup
📂 playwright.config.js - Basic Configuration
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// Test directory
testDir: './tests',
// Global test timeout
timeout: 30 * 1000, // 30 seconds
// Expect timeout
expect: {
timeout: 5000, // 5 seconds
},
// Retry failed tests
retries: 1, // Retry once on failure
// Number of workers (parallel execution)
workers: 3, // Run 3 tests in parallel
// Reporter configuration
reporter: [
['html'],
['json', { outputFile: 'test-results.json' }]
],
// Global test options
use: {
// Base URL for all tests
baseURL: 'https://learn-playwright.great-site.net',
// Browser context options
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
// Viewport size
viewport: { width: 1280, height: 720 },
// Ignore HTTPS errors
ignoreHTTPSErrors: true,
},
// Projects for different browsers/devices
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
Step 2: Command Line Configuration Options
Using --config Flag
# Use a different config file npx playwright test --config=playwright-mobile.config.js # Use specific project npx playwright test --project=chromium # Use specific device npx playwright test --project="iPhone 12" # Run with specific workers npx playwright test --workers=5 # Run with specific retries npx playwright test --retries=2 # Run in headed mode (see browser) npx playwright test --headed # Run in debug mode npx playwright test --debug
Step 3: Viewport Configuration
Different Viewport Settings
// Method 1: Global viewport in config
use: {
viewport: { width: 1920, height: 1080 }, // Full HD
}
// Method 2: Per-test viewport
test('Mobile viewport test', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 }); // iPhone SE
await page.goto('https://learn-playwright.great-site.net');
// Test mobile-specific elements
await expect(page.locator('.mobile-menu')).toBeVisible();
});
// Method 3: Dynamic viewport
test('Responsive design test', async ({ page }) => {
const viewports = [
{ width: 320, height: 568 }, // iPhone 5
{ width: 768, height: 1024 }, // iPad
{ width: 1920, height: 1080 } // Desktop
];
for (const viewport of viewports) {
await page.setViewportSize(viewport);
await page.goto('https://learn-playwright.great-site.net');
console.log(`Testing viewport: ${viewport.width}x${viewport.height}`);
// Add viewport-specific assertions
}
});
Step 4: Device Configuration
📂 playwright.config.js - Device Projects
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
// Desktop browsers
{
name: 'Desktop Chrome',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'Desktop Firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'Desktop Safari',
use: { ...devices['Desktop Safari'] },
},
// Mobile devices
{
name: 'iPhone 12',
use: { ...devices['iPhone 12'] },
},
{
name: 'iPhone 12 Pro',
use: { ...devices['iPhone 12 Pro'] },
},
{
name: 'Pixel 5',
use: { ...devices['Pixel 5'] },
},
// Tablet devices
{
name: 'iPad',
use: { ...devices['iPad'] },
},
{
name: 'iPad Pro',
use: { ...devices['iPad Pro'] },
},
// Custom device
{
name: 'Custom Mobile',
use: {
...devices['iPhone 12'],
viewport: { width: 414, height: 896 },
userAgent: 'Custom Mobile Browser',
},
},
],
});
Step 5: Permissions Configuration
Setting Browser Permissions
// Method 1: Global permissions in config
use: {
permissions: ['geolocation', 'camera', 'microphone'],
geolocation: { latitude: 40.7128, longitude: -74.0060 }, // New York
}
// Method 2: Per-test permissions
test('Location-based test', async ({ page, context }) => {
// Grant geolocation permission
await context.grantPermissions(['geolocation']);
// Set location
await context.setGeolocation({ latitude: 37.7749, longitude: -122.4194 }); // San Francisco
await page.goto('https://maps.google.com');
// Test location-based functionality
await page.click('[aria-label="Your location"]');
await expect(page.locator('text=San Francisco')).toBeVisible();
});
// Method 3: Camera and microphone permissions
test('Video call test', async ({ page, context }) => {
await context.grantPermissions(['camera', 'microphone']);
await page.goto('https://meet.google.com');
// Test camera/microphone access
await page.click('[aria-label="Turn on camera"]');
await expect(page.locator('.camera-preview')).toBeVisible();
});
Step 6: Retries Configuration
Retry Strategies
// Method 1: Global retries in config
export default defineConfig({
retries: 1, // Retry once on failure
});
// Method 2: Per-project retries
projects: [
{
name: 'chromium',
retries: 2, // Retry twice for Chrome
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
retries: 0, // No retries for Firefox
use: { ...devices['Desktop Firefox'] },
},
]
// Method 3: Per-test retries
test('Flaky test with retries', async ({ page }) => {
// This test will retry if it fails
await page.goto('https://learn-playwright.great-site.net');
// Sometimes this might fail due to network issues
await page.click('.unreliable-button');
await expect(page.locator('.success-message')).toBeVisible();
});
// Method 4: Conditional retries
test('Network-dependent test', async ({ page }) => {
// Retry logic within test
let retries = 3;
while (retries > 0) {
try {
await page.goto('https://unreliable-api.com');
await expect(page.locator('.api-response')).toBeVisible();
break; // Success, exit retry loop
} catch (error) {
retries--;
if (retries === 0) throw error;
console.log(`Retrying... ${retries} attempts left`);
await page.waitForTimeout(1000);
}
}
});
Part 3: Parallel Running & Workers (30 min)
What is Parallel Running? (Simple Explanation)
Parallel Running is like having multiple workers doing different tasks at the same time, instead of one worker doing all tasks one by one.
Think of it this way:
Think of it this way:
- 🐌 Sequential (1 worker) = One person washing dishes one by one
- 🚀 Parallel (3 workers) = Three people washing different dishes simultaneously
- ⏰ Time Saved = 3x faster execution
Step 1: Workers Configuration
Setting Up Workers
// Method 1: Global workers in config
export default defineConfig({
workers: 3, // Run 3 tests in parallel
});
// Method 2: Command line workers
npx playwright test --workers=5
// Method 3: Percentage of CPU cores
export default defineConfig({
workers: '50%', // Use 50% of available CPU cores
});
// Method 4: Auto-detect optimal workers
export default defineConfig({
workers: process.env.CI ? 2 : undefined, // 2 workers in CI, auto-detect locally
});
Step 2: Serial vs Parallel Test Execution
📂 tests/parallel-vs-serial.spec.js
import { test, expect } from '@playwright/test';
// PARALLEL EXECUTION (Default)
test.describe('Parallel Tests', () => {
test('Test 1 - Login', async ({ page }) => {
console.log('🚀 Starting Test 1 at:', new Date().toISOString());
await page.goto('https://learn-playwright.great-site.net');
await page.click('text=Sign in');
await page.fill('#email', 'test@example.com');
await page.fill('#password', 'password123');
await page.click('button[type="submit"]');
await expect(page.locator('.welcome-message')).toBeVisible();
console.log('✅ Test 1 completed at:', new Date().toISOString());
});
test('Test 2 - Search', async ({ page }) => {
console.log('🚀 Starting Test 2 at:', new Date().toISOString());
await page.goto('https://learn-playwright.great-site.net');
await page.fill('[placeholder*="Search"]', 'dress');
await page.press('[placeholder*="Search"]', 'Enter');
await expect(page.locator('.search-results')).toBeVisible();
console.log('✅ Test 2 completed at:', new Date().toISOString());
});
test('Test 3 - Cart', async ({ page }) => {
console.log('🚀 Starting Test 3 at:', new Date().toISOString());
await page.goto('https://learn-playwright.great-site.net');
await page.click('.add-to-cart');
await expect(page.locator('.cart-count')).toContainText('1');
console.log('✅ Test 3 completed at:', new Date().toISOString());
});
});
// SERIAL EXECUTION (One after another)
test.describe.serial('Serial Tests', () => {
test('Step 1 - Setup', async ({ page }) => {
console.log('🚀 Starting Step 1 at:', new Date().toISOString());
await page.goto('https://learn-playwright.great-site.net');
await page.click('text=Sign in');
await page.fill('#email', 'test@example.com');
await page.fill('#password', 'password123');
await page.click('button[type="submit"]');
console.log('✅ Step 1 completed at:', new Date().toISOString());
});
test('Step 2 - Add Product', async ({ page }) => {
console.log('🚀 Starting Step 2 at:', new Date().toISOString());
// This test depends on Step 1 being completed
await expect(page.locator('.welcome-message')).toBeVisible();
await page.click('.product-card');
await page.click('.add-to-cart');
console.log('✅ Step 2 completed at:', new Date().toISOString());
});
test('Step 3 - Checkout', async ({ page }) => {
console.log('🚀 Starting Step 3 at:', new Date().toISOString());
// This test depends on Step 2 being completed
await expect(page.locator('.cart-count')).toContainText('1');
await page.click('.checkout-button');
await expect(page.locator('.order-summary')).toBeVisible();
console.log('✅ Step 3 completed at:', new Date().toISOString());
});
});
Step 3: Test Tags and Filtering
Using --grep for Test Filtering
import { test, expect } from '@playwright/test';
// Tag tests with different categories
test.describe('Login Tests @smoke @critical', () => {
test('Valid login @positive', async ({ page }) => {
await page.goto('https://learn-playwright.great-site.net');
await page.click('text=Sign in');
await page.fill('#email', 'valid@example.com');
await page.fill('#password', 'validpassword');
await page.click('button[type="submit"]');
await expect(page.locator('.welcome-message')).toBeVisible();
});
test('Invalid login @negative', async ({ page }) => {
await page.goto('https://learn-playwright.great-site.net');
await page.click('text=Sign in');
await page.fill('#email', 'invalid@example.com');
await page.fill('#password', 'wrongpassword');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toBeVisible();
});
});
test.describe('Search Tests @regression', () => {
test('Search by product name @search', async ({ page }) => {
await page.goto('https://learn-playwright.great-site.net');
await page.fill('[placeholder*="Search"]', 'dress');
await page.press('[placeholder*="Search"]', 'Enter');
await expect(page.locator('.search-results')).toBeVisible();
});
test('Search by category @search', async ({ page }) => {
await page.goto('https://learn-playwright.great-site.net');
await page.click('text=Clothes');
await expect(page.locator('.category-products')).toBeVisible();
});
});
test.describe('Cart Tests @e2e', () => {
test('Add to cart @cart', async ({ page }) => {
await page.goto('https://learn-playwright.great-site.net');
await page.click('.product-card');
await page.click('.add-to-cart');
await expect(page.locator('.cart-count')).toContainText('1');
});
test('Remove from cart @cart', async ({ page }) => {
await page.goto('https://learn-playwright.great-site.net');
await page.click('.product-card');
await page.click('.add-to-cart');
await page.click('.remove-from-cart');
await expect(page.locator('.cart-count')).toContainText('0');
});
});
Step 4: Command Line Test Filtering
Using --grep with Different Patterns
# Run only smoke tests npx playwright test --grep @smoke # Run only critical tests npx playwright test --grep @critical # Run tests with specific tag npx playwright test --grep @login # Run tests matching multiple tags npx playwright test --grep "@smoke|@critical" # Run tests excluding certain tags npx playwright test --grep "@smoke --grep-invert @slow" # Run tests with specific test name pattern npx playwright test --grep "login" # Run tests with regex pattern npx playwright test --grep ".*cart.*" # Run tests in specific file npx playwright test tests/login.spec.js --grep @positive # Run tests with specific project and tag npx playwright test --project=chromium --grep @smoke # Run tests with workers and tags npx playwright test --workers=5 --grep @regression
Step 5: Advanced Configuration Examples
📂 playwright.config.js - Complete Configuration
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// Test settings
testDir: './tests',
timeout: 30 * 1000,
expect: { timeout: 5000 },
retries: 1,
workers: 3,
// Reporter configuration
reporter: [
['html', { outputFolder: 'playwright-report' }],
['json', { outputFile: 'test-results.json' }],
['junit', { outputFile: 'test-results.xml' }],
['line']
],
// Global test options
use: {
baseURL: 'https://learn-playwright.great-site.net',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
permissions: ['geolocation'],
geolocation: { latitude: 40.7128, longitude: -74.0060 },
},
// Projects for different environments
projects: [
// Desktop browsers
{
name: 'Desktop Chrome',
use: { ...devices['Desktop Chrome'] },
retries: 1,
},
{
name: 'Desktop Firefox',
use: { ...devices['Desktop Firefox'] },
retries: 0,
},
{
name: 'Desktop Safari',
use: { ...devices['Desktop Safari'] },
retries: 1,
},
// Mobile devices
{
name: 'iPhone 12',
use: { ...devices['iPhone 12'] },
retries: 2,
},
{
name: 'Pixel 5',
use: { ...devices['Pixel 5'] },
retries: 2,
},
// Tablet
{
name: 'iPad',
use: { ...devices['iPad'] },
retries: 1,
},
// Custom configurations
{
name: 'High Resolution',
use: {
...devices['Desktop Chrome'],
viewport: { width: 2560, height: 1440 },
},
},
{
name: 'Low Resolution',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1024, height: 768 },
},
},
],
// Web server for local testing
webServer: {
command: 'npm run start',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
🧑💻 Interactive Questions & Answers
1. Why do iframes require special handling in Playwright?
Answer:
- Separate Context: Iframes create isolated browsing contexts within the main page
- Security Boundaries: Cross-origin iframes have restricted access for security reasons
- DOM Isolation: Elements inside iframes are not directly accessible from the main page
- Loading Timing: Iframes load independently and may not be ready when the main page loads
- Special Navigation: Playwright needs to switch context to interact with iframe content
2. What's the difference between workers and retries?
Answer:
- Workers: Number of tests running simultaneously (parallel execution)
- Retries: Number of times a failed test is re-executed
- Workers Example: 3 workers = 3 tests running at the same time
- Retries Example: retries: 1 = if test fails, run it once more
- Combined: You can have 3 workers running tests, and each test can retry once if it fails
3. When should you use serial vs parallel test execution?
Answer:
- Use Serial When:
- Tests depend on each other (setup → action → verification)
- Testing stateful applications (user sessions, database changes)
- Limited resources (API rate limits, database connections)
- Use Parallel When:
- Tests are independent of each other
- Testing different features or pages
- Want faster execution time
4. How do you choose the right number of workers?
Answer:
- CPU Cores: Start with number of CPU cores (e.g., 4 cores = 4 workers)
- Memory: Each worker uses memory, don't exceed available RAM
- Network: Too many workers can overwhelm the server
- Test Complexity: Heavy tests need fewer workers, light tests can use more
- CI Environment: Use fewer workers in CI (e.g., 2-3) to avoid resource conflicts
5. What are the benefits of using test tags with --grep?
Answer:
- Selective Testing: Run only specific test categories (smoke, regression, critical)
- Faster Feedback: Run quick smoke tests during development
- CI/CD Optimization: Different test suites for different stages
- Debugging: Run only failing test categories
- Resource Management: Run heavy tests only when needed
🎯 Day 13 Homework Tasks
🟢 Beginner
Task 1
Create a test that interacts with an iframe element using frameLocator().
Task 2
Set up basic playwright.config.js with 2 workers and 1 retry.
🟡 Intermediate
Task 3
Create tests with different viewport sizes and verify responsive behavior.
Task 4
Set up projects for desktop and mobile devices with different retry settings.
🔴 Advanced
Task 5
Create a test suite with tags (@smoke, @regression, @e2e) and run specific subsets using --grep.
Task 6
Implement serial and parallel test execution patterns and measure performance differences.
Recommended Test Websites
Best Websites for Iframe and Config Testing:
- Iframe Testing:
https://the-internet.herokuapp.com/iframe - Nested Frames:
https://the-internet.herokuapp.com/nested_frames - Frames:
https://the-internet.herokuapp.com/frames - Responsive Design:
https://learn-playwright.great-site.net - Geolocation:
https://the-internet.herokuapp.com/geolocation
✅ Outcomes
- Master iframe handling with frameLocator() and context switching
- Configure Playwright with advanced options (viewport, devices, permissions)
- Set up parallel and serial test execution strategies
- Use test tags and --grep for selective test execution
- Optimize test performance with proper worker configuration
- Handle different devices and viewport sizes effectively
- Implement retry strategies for flaky tests
- Create comprehensive test configurations for different environments