Copy-paste ready code for your test automation — page objects, fixtures, custom commands, and more. Select a framework and find what you need.
>search snippets…⌘K
80
Snippets
5
Frameworks
Framework
80 snippets
// 🧪 Cypress · 22 snippets
Click element by data-testid
CypressTS
Target elements reliably using data-testid attributes — the recommended approach for test selectors.
// Single clickcy.get('[data-testid="submit-btn"]').click();// Double-clickcy.get('[data-testid="editable-cell"]').dblclick();// Right-click to open context menucy.get('[data-testid="context-item"]').rightclick();// Click inside a scoped areacy.get('[data-testid="user-card"]').within(() => { cy.get('[data-testid="edit-btn"]').click();});
Type into an input field
CypressTS
Fill form fields and trigger keyboard events, including special keys like Enter and Tab.
// Clear and typecy.get('[data-testid="email-input"]').clear().type('user@example.com');// Submit form by pressing Entercy.get('[data-testid="search-input"]').type('test query{enter}');// Tab between fieldscy.get('[data-testid="first-name"]').type('Alice{tab}');cy.get('[data-testid="last-name"]').type('Smith');// Slow typing to simulate a real usercy.get('[data-testid="message"]').type('Hello world', { delay: 80 });
Assert exact text content
CypressTS
Verify that an element's text matches an expected string exactly.
Wait for a specific API call to complete before asserting — prevents flaky tests caused by asserting before data loads.
cy.intercept('GET', '/api/dashboard/stats').as('loadStats');cy.visit('/dashboard');// Wait and assert the responsecy.wait('@loadStats').then((interception) => { expect(interception.response?.statusCode).to.equal(200); expect(interception.response?.body).to.have.property('totalUsers');});// Shorthand status assertioncy.wait('@loadStats').its('response.statusCode').should('equal', 200);// Wait for multiple requests at oncecy.wait(['@loadStats', '@loadProducts']);
Tip: cy.wait('@alias') also retries if the request hasn't arrived yet, giving you automatic resilience.
Upload a file
CypressTS
Select a file on a file input using selectFile — works with fixtures, paths, and programmatic File objects.
// Upload a single file from the fixtures directorycy.get('input[type="file"]').selectFile('cypress/fixtures/sample-report.pdf');// Upload multiple filescy.get('input[type="file"]').selectFile([ 'cypress/fixtures/screenshot-1.png', 'cypress/fixtures/screenshot-2.png',]);// Simulate drag-and-drop onto a dropzonecy.get('[data-testid="dropzone"]').selectFile( 'cypress/fixtures/test-data.csv', { action: 'drag-drop' });// Assert the uploaded file name appearscy.get('[data-testid="file-name"]').should('contain.text', 'sample-report.pdf');
Programmatic login via cy.request
CypressTS
Log in by calling the auth API directly — much faster than driving the UI login form in every test.
// beforeEach in your test filebeforeEach(() => { cy.request({ method: 'POST', url: '/api/auth/login', body: { email: 'user@example.com', password: 'password123' }, }).then((response) => { expect(response.status).to.equal(200); // Store the token so page requests include it window.localStorage.setItem('authToken', response.body.token); cy.setCookie('session', response.body.sessionId); });});
Tip: Use cy.session() in Cypress 12+ to cache and replay login state, avoiding the request on every test.
Custom login command
CypressTS
Add a reusable cy.login() command so every test can authenticate in one line.
Verify the browser is on the expected URL after a login redirect, form submission, or link click.
// Exact URL matchcy.url().should('equal', 'https://app.example.com/dashboard');// Partial match (good for dynamic IDs in the path)cy.url().should('include', '/dashboard');// Regex matchcy.url().should('match', /\/orders\/[a-z0-9-]+\/confirmation/);// After clicking a linkcy.get('[data-testid="view-profile-link"]').click();cy.url().should('include', '/profile');
Assert element count
CypressTS
Verify how many matching elements exist in the DOM — useful for lists, table rows, and search results.
// Exact countcy.get('[data-testid="product-card"]').should('have.length', 12);// At least one resultcy.get('.search-result').should('have.length.greaterThan', 0);// No resultscy.get('[data-testid="user-row"]').should('have.length', 0);// Between min and max (via custom assertion)cy.get('li.notification-item').then(($items) => { expect($items.length).to.be.within(1, 10);});
Set viewport for responsive testing
CypressTS
Switch between desktop, tablet, and mobile viewport sizes to test responsive layouts.
// Use a named device presetcy.viewport('iphone-14');cy.viewport('ipad-2');// Use explicit dimensions (width, height)cy.viewport(375, 812); // iPhone SEcy.viewport(1440, 900); // Large desktop// Set a default in cypress.config.ts// export default defineConfig({// viewportWidth: 1280,// viewportHeight: 720,// });// Test across multiple sizes in one specconst sizes: Cypress.ViewportPreset[] = ['iphone-14', 'ipad-2'];sizes.forEach((size) => { it(`renders nav on ${size}`, () => { cy.viewport(size); cy.visit('/'); cy.get('[data-testid="nav"]').should('be.visible'); });});
Interact with iframe content
CypressTS
Access elements inside a same-origin iframe by drilling into its content document.
// Access iframe body and interact with itcy.get('iframe[data-testid="payment-frame"]') .its('0.contentDocument.body') .should('not.be.empty') .then(cy.wrap) .find('[data-testid="card-number"]') .type('4111111111111111');// With a helper command (add to commands.ts)Cypress.Commands.add('getIframeBody', (selector: string) => { return cy .get(selector) .its('0.contentDocument.body') .should('not.be.empty') .then(cy.wrap);});// Usagecy.getIframeBody('[data-testid="embed-frame"]').find('button').click();
Tip: This approach only works for same-origin iframes. Cross-origin iframes require a different strategy (cy.origin).
Assert CSS property value
CypressTS
Verify computed CSS styles — useful for colour, font size, visibility, and layout assertions.
// Assert background colour (use rgb() format)cy.get('[data-testid="error-banner"]') .should('have.css', 'background-color', 'rgb(239, 68, 68)');// Assert element is not visible via opacity or displaycy.get('[data-testid="tooltip"]') .should('have.css', 'opacity', '0');// Assert font sizecy.get('[data-testid="price"]') .should('have.css', 'font-size', '24px');// Assert using invoke for computed valuescy.get('[data-testid="sidebar"]') .invoke('css', 'width') .then(parseInt) .should('be.greaterThan', 200);
Tip: Browsers return colour values as rgb() strings, not hex. Convert your hex colours to rgb before asserting.
Assert table row data
CypressTS
Read and verify data in table rows — check individual cells or loop through all rows.
// Assert specific cells in the first rowcy.get('table tbody tr').first().within(() => { cy.get('td').eq(0).should('contain.text', 'Alice Smith'); cy.get('td').eq(1).should('contain.text', 'alice@example.com'); cy.get('td').eq(2).should('contain.text', 'Admin');});// Assert row countcy.get('table tbody tr').should('have.length', 10);// Find a row by cell contentcy.contains('table tbody tr', 'alice@example.com').within(() => { cy.get('[data-testid="edit-btn"]').should('be.visible');});
Handle window.confirm dialog
CypressTS
Automatically accept or reject browser confirm dialogs triggered by your application.
// Accept the confirm dialog (returns true)cy.on('window:confirm', () => true);cy.get('[data-testid="delete-btn"]').click();cy.get('[data-testid="item-row"]').should('not.exist');// Reject the confirm dialog (returns false)cy.on('window:confirm', () => false);cy.get('[data-testid="delete-btn"]').click();cy.get('[data-testid="item-row"]').should('still.exist');// Spy on the confirm callconst stub = cy.stub();cy.on('window:confirm', stub);cy.get('[data-testid="delete-btn"]').click().then(() => { expect(stub).to.have.been.calledWith('Are you sure you want to delete this item?');});
Take a named screenshot
CypressTS
Capture screenshots at any point in a test — full page or scoped to a specific element.
// Full-page screenshotcy.screenshot('login-page-before-submit');// Screenshot of a specific element onlycy.get('[data-testid="error-summary"]').screenshot('validation-errors');// Screenshot with optionscy.screenshot('dashboard-state', { capture: 'fullPage', overwrite: true,});// In a custom command — automatic screenshot on failureCypress.on('test:after:run', (test) => { if (test.state === 'failed') { const name = test.fullTitle.replace(/\s+/g, '-'); cy.screenshot(name, { capture: 'fullPage' }); }});
Read and use environment variables
CypressTS
Access config values from cypress.env.json or cypress.config.ts env block — no hard-coded credentials.
// cypress.config.tsimport { defineConfig } from 'cypress';export default defineConfig({ e2e: { baseUrl: 'https://staging.example.com', }, env: { apiUrl: 'https://api.staging.example.com', adminEmail: 'admin@example.com', },});// In a testconst apiUrl = Cypress.env('apiUrl') as string;const adminEmail = Cypress.env('adminEmail') as string;cy.request(`${apiUrl}/health`) .its('status') .should('equal', 200);// Override at runtime: cypress run --env apiUrl=https://prod.example.com
Trigger a keyboard shortcut
CypressTS
Fire keyboard shortcuts and hotkeys against the body or a focused element.
// Open a command palette (Cmd+K / Ctrl+K)cy.get('body').type('{meta}k'); // macOScy.get('body').type('{ctrl}k'); // Windows/Linux// Close a modal with Escapecy.get('[data-testid="modal"]').should('be.visible');cy.get('body').type('{esc}');cy.get('[data-testid="modal"]').should('not.exist');// Select all and deletecy.get('[data-testid="editor"]').focus().type('{selectAll}{del}');// Ctrl+Z undocy.get('[data-testid="editor"]').focus().type('{ctrl}z');// Arrow key navigationcy.get('[data-testid="dropdown"]').focus().type('{downarrow}{downarrow}{enter}');
Set localStorage values before page visit
CypressTS
Seed localStorage with tokens or feature flags before cy.visit() — avoids login redirects and lets you test authenticated states without going through the UI.
// Seed localStorage before the page loadscy.visit('/dashboard', { onBeforeLoad(win) { win.localStorage.setItem('auth_token', 'fake-jwt-abc123'); win.localStorage.setItem('user_id', '42'); win.localStorage.setItem('feature_flags', JSON.stringify({ newNav: true })); },});// Alternative: use cy.window() after visitcy.window().then((win) => { win.localStorage.setItem('theme', 'dark');});// Assert a value was setcy.window().its('localStorage').invoke('getItem', 'auth_token').should('eq', 'fake-jwt-abc123');
Wait for and retry a flaky network intercept
CypressTS
Alias an intercept with .as() then use cy.wait() to pause until the matched request completes — eliminates timing-based waits for API calls that may be slow.
// Intercept and alias the requestcy.intercept('GET', '/api/reports*').as('fetchReports');cy.visit('/reports');// Wait for the aliased request (default timeout applies)cy.wait('@fetchReports').then(({ response }) => { expect(response!.statusCode).to.equal(200); expect(response!.body.data).to.have.length.greaterThan(0);});// Wait for multiple sequential calls to the same endpointcy.intercept('POST', '/api/search').as('search');cy.get('[data-testid="search-input"]').type('cypress');cy.wait('@search');cy.get('[data-testid="search-input"]').clear().type('playwright');cy.wait('@search');// Assert total call countcy.get('@search.all').should('have.length', 2);
// 🎭 Playwright · 22 snippets
Wait for element to be visible
PlaywrightTS
Assert visibility before interacting — Playwright auto-waits but explicit checks prevent race conditions.
import { test, expect } from '@playwright/test';test('dashboard loads data', async ({ page }) => { await page.goto('/dashboard'); // Wait for the main content to appear await expect(page.getByTestId('dashboard-grid')).toBeVisible(); // Wait for a loading spinner to disappear await expect(page.getByTestId('spinner')).toBeHidden({ timeout: 10_000 }); // Wait for an element and get its text const heading = page.getByRole('heading', { level: 1 }); await expect(heading).toBeVisible(); await expect(heading).toHaveText('My Dashboard');});
Click using semantic role locators
PlaywrightTS
getByRole is the most resilient selector — it matches elements the same way assistive technology does.
// Click a button by its accessible nameawait page.getByRole('button', { name: 'Submit' }).click();// Click a linkawait page.getByRole('link', { name: 'Sign in' }).click();// Click a menu itemawait page.getByRole('menuitem', { name: 'Delete account' }).click();// Exact vs partial name matchawait page.getByRole('button', { name: 'Save changes', exact: true }).click();// Click a checkboxawait page.getByRole('checkbox', { name: 'Accept terms' }).check();// Click inside a specific regionawait page .getByRole('region', { name: 'Billing details' }) .getByRole('button', { name: 'Edit' }) .click();
Fill form fields using getByLabel
PlaywrightTS
Target inputs by their visible label text — the cleanest approach that ties directly to user experience.
import { test, expect } from '@playwright/test';test('fill registration form', async ({ page }) => { await page.goto('/register'); await page.getByLabel('Full name').fill('Alice Smith'); await page.getByLabel('Email address').fill('alice@example.com'); await page.getByLabel('Password').fill('SecurePass123!'); await page.getByLabel('Confirm password').fill('SecurePass123!'); // Select from a dropdown await page.getByLabel('Country').selectOption('GB'); // Check a checkbox by its label await page.getByLabel('I agree to the terms').check(); await page.getByRole('button', { name: 'Create account' }).click(); await expect(page).toHaveURL(/\/welcome/);});
Assert text content
PlaywrightTS
Verify element text with exact match, partial match, or regex — all auto-retry until the assertion passes.
// Exact matchawait expect(page.getByTestId('status-badge')).toHaveText('Active');// Partial matchawait expect(page.getByTestId('notification')).toContainText('updated successfully');// Regex matchawait expect(page.getByTestId('invoice-number')).toHaveText(/INV-\d+/);// Assert text on all matching elementsawait expect(page.getByRole('listitem')).toHaveText(['Apple', 'Banana', 'Cherry']);// Read text into a variableconst price = await page.getByTestId('total-price').textContent();console.log(price?.trim()); // '£29.99'
Mock an API with page.route
PlaywrightTS
Intercept network requests and return controlled responses — essential for testing loading states and error handling.
Tip: Add tests/.auth/ to .gitignore — it contains session cookies and should never be committed.
Assert the current URL
PlaywrightTS
Check the browser URL after navigation, redirects, or form submissions.
// Exact URL matchawait expect(page).toHaveURL('https://app.example.com/dashboard');// Partial match (contains a substring)await expect(page).toHaveURL(/\/dashboard/);// After a form submission redirectawait page.getByRole('button', { name: 'Log in' }).click();await expect(page).toHaveURL(/.*\/dashboard/, { timeout: 5000 });// After clicking a linkawait page.getByRole('link', { name: 'View profile' }).click();await expect(page).toHaveURL(/\/users\/[a-z0-9-]+\/profile/);
Assert element count
PlaywrightTS
Verify the number of matching elements — toHaveCount retries automatically until the condition is met.
// Exact countawait expect(page.getByTestId('product-card')).toHaveCount(12);// No resultsawait expect(page.getByRole('row', { name: /invoice/ })).toHaveCount(0);// Count manuallyconst items = page.getByRole('listitem');const count = await items.count();expect(count).toBeGreaterThan(0);// Assert each item has textfor (let i = 0; i < count; i++) { await expect(items.nth(i)).not.toBeEmpty();}
Handle multiple tabs and windows
PlaywrightTS
Wait for a new tab to open after a click and interact with it.
import { test, expect } from '@playwright/test';test('opens report in a new tab', async ({ page, context }) => { await page.goto('/reports'); // Wait for new page event triggered by the click const [newPage] = await Promise.all([ context.waitForEvent('page'), page.getByRole('link', { name: 'Open report' }).click(), ]); await newPage.waitForLoadState('domcontentloaded'); await expect(newPage).toHaveURL(/\/reports\/[0-9]+/); await expect(newPage.getByRole('heading', { level: 1 })).toBeVisible(); await newPage.close(); // Original page is still open await expect(page).toHaveURL(/\/reports/);});
Take screenshots
PlaywrightTS
Capture the full page or a single element for debugging or visual reference.
// Full page screenshotawait page.screenshot({ path: 'screenshots/full-page.png', fullPage: true });// Viewport screenshot onlyawait page.screenshot({ path: 'screenshots/viewport.png' });// Screenshot of a single elementawait page.getByTestId('invoice-summary').screenshot({ path: 'screenshots/invoice-summary.png',});// Return as a Buffer (for comparison or upload)const buffer = await page.screenshot();console.log('Screenshot size:', buffer.length, 'bytes');// Screenshot on test failure (configure in playwright.config.ts)// use: { screenshot: 'only-on-failure' }
API GET request with request fixture
PlaywrightTS
Make API calls directly in a test using Playwright's built-in request context — no browser needed.
import { test, expect } from '@playwright/test';test('GET /api/users returns 200 and a users array', async ({ request }) => { const response = await request.get('/api/users', { headers: { Authorization: `Bearer ${process.env.API_TOKEN}` }, }); expect(response.status()).toBe(200); const body = await response.json(); expect(body).toHaveProperty('users'); expect(Array.isArray(body.users)).toBe(true); expect(body.users.length).toBeGreaterThan(0); // Assert shape of first item const [firstUser] = body.users; expect(firstUser).toMatchObject({ id: expect.any(String), email: expect.stringContaining('@'), });});
Soft assertions — collect all failures
PlaywrightTS
Soft assertions don't stop the test on failure — all run so you see every issue in a single test run.
import { test, expect } from '@playwright/test';test('dashboard displays all key widgets', async ({ page }) => { await page.goto('/dashboard'); // These all run even if some fail await expect.soft(page.getByTestId('revenue-widget')).toBeVisible(); await expect.soft(page.getByTestId('active-users-widget')).toBeVisible(); await expect.soft(page.getByTestId('orders-widget')).toBeVisible(); await expect.soft(page.getByTestId('conversion-rate')).toContainText('%'); // Hard assertion at the end — fails the test if any soft assertion failed expect(test.info().errors).toHaveLength(0);});
Tip: Use soft assertions on dashboards or multi-component pages where you want a full inventory of failures at once.
Custom test fixture
PlaywrightTS
Extend test with shared setup/teardown fixtures so every test in a suite starts in the right state.
// tests/fixtures.tsimport { test as base, expect, type Page } from '@playwright/test';type Fixtures = { authenticatedPage: Page;};export const test = base.extend<Fixtures>({ authenticatedPage: async ({ page }, use) => { // Setup: log in before each test await page.goto('/login'); await page.getByLabel('Email').fill('user@example.com'); await page.getByLabel('Password').fill('password123'); await page.getByRole('button', { name: 'Log in' }).click(); await expect(page).toHaveURL(/dashboard/); // Hand the logged-in page to the test await use(page); // Teardown: runs after the test await page.evaluate(() => localStorage.clear()); },});export { expect };// Usage in a spec// import { test, expect } from './fixtures';// test('can view profile', async ({ authenticatedPage }) => { ... });
Visual regression with toHaveScreenshot
PlaywrightTS
Compare screenshots pixel-by-pixel against a stored baseline — catch unintended visual regressions.
import { test, expect } from '@playwright/test';test('landing page matches baseline', async ({ page }) => { await page.goto('/'); await page.waitForLoadState('networkidle'); // First run creates the snapshot; subsequent runs compare await expect(page).toHaveScreenshot('landing-page.png', { maxDiffPixelRatio: 0.02, // Allow up to 2% pixel difference });});test('card component matches baseline', async ({ page }) => { await page.goto('/components/card'); await expect(page.getByTestId('feature-card')).toHaveScreenshot( 'feature-card.png', { threshold: 0.1 } );});// Update snapshots: npx playwright test --update-snapshots
Get text content from elements
PlaywrightTS
Read text from elements into variables for complex assertions or downstream logic.
// Single element textconst heading = await page.getByRole('heading', { level: 1 }).textContent();console.log(heading?.trim()); // 'Welcome to Dashboard'// innerText (respects CSS visibility, normalises whitespace)const price = await page.getByTestId('total-price').innerText();console.log(price); // '£29.99'// All matching element texts as an arrayconst labels = await page.getByRole('listitem').allTextContents();console.log(labels); // ['Item 1', 'Item 2', 'Item 3']// Assert after readingconst badge = await page.getByTestId('status').textContent();expect(badge?.trim()).toBe('Active');
Hover to reveal a hidden element, then click it
PlaywrightTS
Hover over a parent element to make hidden children appear, then interact with them.
import { test, expect } from '@playwright/test';test('edit item from hover menu', async ({ page }) => { await page.goto('/items'); const row = page.getByTestId('item-row').first(); // Hover to reveal the action buttons await row.hover(); // Now click the Edit button that appeared on hover await page.getByTestId('row-action-edit').click(); await expect(page.getByRole('dialog', { name: 'Edit item' })).toBeVisible(); // Hover over a nav item to reveal a dropdown await page.getByRole('link', { name: 'Products' }).hover(); await page.getByRole('menuitem', { name: 'All categories' }).click();});
Multi-browser and viewport config
PlaywrightTS
Run the same tests across desktop Chrome, mobile Safari, and tablets by declaring projects in the config.
Abort requests to ad networks and analytics domains before they fire — speeds up tests, removes flakiness from third-party slowdowns, and keeps network logs clean.
Use @axe-core/playwright to scan the page for WCAG violations and surface them as a readable test failure — add to smoke tests to catch regressions early.
import { test, expect } from '@playwright/test';import AxeBuilder from '@axe-core/playwright';test('homepage has no critical accessibility violations', async ({ page }) => { await page.goto('/'); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) // WCAG 2.1 AA .exclude('#legacy-widget') // opt-out known third-party element .analyze(); // Format violations for a readable failure message const violations = results.violations.map((v) => ({ id: v.id, impact: v.impact, description: v.description, nodes: v.nodes.length, })); expect(violations, JSON.stringify(violations, null, 2)).toHaveLength(0);});
// 🌐 Selenium · 14 snippets
WebDriver setup with ChromeOptions (Java)
SeleniumJava
Create a Chrome WebDriver instance with common options for test runs.
import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.chrome.ChromeOptions;public class BrowserFactory { public static WebDriver createChromeDriver() { ChromeOptions options = new ChromeOptions(); options.addArguments("--disable-notifications"); options.addArguments("--disable-popup-blocking"); options.addArguments("--window-size=1280,720"); // Uncomment for headless runs in CI // options.addArguments("--headless=new"); // options.addArguments("--no-sandbox"); // options.addArguments("--disable-dev-shm-usage"); return new ChromeDriver(options); }}
WebDriver setup (Python)
SeleniumPy
Create a Chrome WebDriver with common options and implicit wait configured.
from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsdef create_driver() -> webdriver.Chrome: options = Options() options.add_argument('--disable-notifications') options.add_argument('--window-size=1280,720') # Uncomment for headless CI runs # options.add_argument('--headless=new') # options.add_argument('--no-sandbox') # options.add_argument('--disable-dev-shm-usage') driver = webdriver.Chrome(options=options) driver.implicitly_wait(10) # seconds return driver# Usage with pytestimport pytest@pytest.fixturedef driver(): d = create_driver() yield d d.quit()
Find element by CSS selector or XPath (Java)
SeleniumJava
Locate elements using CSS selectors (preferred) or XPath for text-based lookups.
import org.openqa.selenium.By;import org.openqa.selenium.WebElement;// CSS selector — preferred for most casesWebElement submitBtn = driver.findElement(By.cssSelector("[data-testid='submit-btn']"));submitBtn.click();// By IDWebElement email = driver.findElement(By.id("email-input"));email.sendKeys("user@example.com");// By class nameWebElement card = driver.findElement(By.className("product-card"));// XPath — use when CSS can't target by textWebElement link = driver.findElement(By.xpath("//a[contains(text(),'Sign in')]"));WebElement cell = driver.findElement(By.xpath("//td[text()='Active']/following-sibling::td"));
Find multiple elements (Java)
SeleniumJava
Retrieve a list of matching elements and iterate over them or assert their count.
import java.util.List;import org.openqa.selenium.By;import org.openqa.selenium.WebElement;// Get all table rowsList<WebElement> rows = driver.findElements(By.cssSelector("table tbody tr"));System.out.println("Row count: " + rows.size());// Assert count (using TestNG or JUnit)assertEquals(10, rows.size());// Read all cell values from column 0for (WebElement row : rows) { String name = row.findElement(By.cssSelector("td:first-child")).getText(); System.out.println(name);}// Find a row containing specific textWebElement targetRow = rows.stream() .filter(r -> r.getText().contains("alice@example.com")) .findFirst() .orElseThrow();
Explicit wait with ExpectedConditions (Java)
SeleniumJava
Wait for specific conditions before interacting — avoids brittle Thread.sleep calls.
import org.openqa.selenium.By;import org.openqa.selenium.WebElement;import org.openqa.selenium.support.ui.ExpectedConditions;import org.openqa.selenium.support.ui.WebDriverWait;import java.time.Duration;WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));// Wait until element is clickableWebElement button = wait.until( ExpectedConditions.elementToBeClickable(By.cssSelector("[data-testid='submit']")));button.click();// Wait for URL to include a path segmentwait.until(ExpectedConditions.urlContains("/dashboard"));// Wait for text to appearwait.until(ExpectedConditions.textToBePresentInElementLocated( By.id("status-message"), "Saved successfully"));// Wait for element to disappearwait.until(ExpectedConditions.invisibilityOfElementLocated( By.cssSelector(".loading-spinner")));
Tip: Set a default implicit wait on the driver (driver.manage().timeouts().implicitlyWait()), but use explicit waits for critical synchronisation points.
Explicit wait with expected_conditions (Python)
SeleniumPy
Wait for dynamic elements to appear or change state before interacting.
from selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECwait = WebDriverWait(driver, timeout=10)# Wait until element is clickablebutton = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "[data-testid='submit']")))button.click()# Wait for URLwait.until(EC.url_contains('/dashboard'))# Wait for text to appearwait.until(EC.text_to_be_present_in_element((By.ID, 'status'), 'Saved'))# Wait for element to disappearwait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, '.spinner')))# Wait for element to be present in DOMelement = wait.until(EC.presence_of_element_located((By.ID, 'results-container')))
Hover over an element (Java)
SeleniumJava
Use Actions to simulate mouse hover and reveal hidden elements before clicking them.
import org.openqa.selenium.By;import org.openqa.selenium.WebElement;import org.openqa.selenium.interactions.Actions;Actions actions = new Actions(driver);// Hover to reveal a dropdown menuWebElement navMenu = driver.findElement(By.cssSelector("[data-testid='products-menu']"));actions.moveToElement(navMenu).perform();// Hover then click a revealed sub-itemWebElement subItem = driver.findElement(By.cssSelector("[data-testid='submenu-electronics']"));actions.moveToElement(navMenu) .moveToElement(subItem) .click() .perform();// Hover to reveal tooltipWebElement icon = driver.findElement(By.cssSelector("[data-testid='info-icon']"));actions.moveToElement(icon).perform();WebElement tooltip = driver.findElement(By.cssSelector("[role='tooltip']"));assertTrue(tooltip.isDisplayed());
Drag and drop (Python)
SeleniumPy
Drag an element from a source to a target using ActionChains.
from selenium.webdriver.common.action_chains import ActionChainsfrom selenium.webdriver.common.by import Bysource = driver.find_element(By.CSS_SELECTOR, "[data-testid='drag-item']")target = driver.find_element(By.CSS_SELECTOR, "[data-testid='drop-zone']")# Simple drag and dropActionChains(driver).drag_and_drop(source, target).perform()# Step-by-step for more controlActionChains(driver) \ .click_and_hold(source) \ .move_to_element(target) \ .release() \ .perform()# Drag to an offset (x, y pixels from element)ActionChains(driver) \ .drag_and_drop_by_offset(source, 200, 0) \ .perform()
Handle a Select dropdown (Java)
SeleniumJava
Select options from a native HTML <select> element by visible text, value, or index.
import org.openqa.selenium.By;import org.openqa.selenium.support.ui.Select;Select dropdown = new Select(driver.findElement(By.id("country-select")));// Select by visible textdropdown.selectByVisibleText("United Kingdom");// Select by value attributedropdown.selectByValue("gb");// Select by index (0-based)dropdown.selectByIndex(2);// Read the selected valueString selected = dropdown.getFirstSelectedOption().getText();System.out.println("Selected: " + selected);// Multi-select: get all selected optionsList<WebElement> selectedOptions = dropdown.getAllSelectedOptions();assertEquals(1, selectedOptions.size());
Switch into an iframe and back (Java)
SeleniumJava
Interact with elements inside a same-origin iframe by switching the driver's context.
import org.openqa.selenium.By;import org.openqa.selenium.WebElement;// Switch into the iframe by elementWebElement frame = driver.findElement(By.cssSelector("iframe[data-testid='payment-frame']"));driver.switchTo().frame(frame);// Interact with elements inside the iframedriver.findElement(By.id("card-number")).sendKeys("4111111111111111");driver.findElement(By.id("card-expiry")).sendKeys("12/26");driver.findElement(By.id("card-cvc")).sendKeys("123");// Switch back to the main documentdriver.switchTo().defaultContent();// Back in the main pagedriver.findElement(By.cssSelector("[data-testid='place-order-btn']")).click();
Tip: Always call driver.switchTo().defaultContent() after finishing with the iframe to avoid NoSuchElementException on subsequent selectors.
Switch to a new tab or window (Java)
SeleniumJava
Switch WebDriver focus to a new tab opened by the application, then return to the original.
// Save the original window handleString originalWindow = driver.getWindowHandle();// Click a link that opens a new window/tabdriver.findElement(By.cssSelector("[data-testid='open-report-link']")).click();// Wait for the new window and switch to itfor (String handle : driver.getWindowHandles()) { if (!handle.equals(originalWindow)) { driver.switchTo().window(handle); break; }}// Interact with the new windowassertTrue(driver.getTitle().contains("Report"));// Close the new window and switch backdriver.close();driver.switchTo().window(originalWindow);assertTrue(driver.getTitle().contains("Dashboard"));
Take a screenshot (Java)
SeleniumJava
Capture the current browser state to a file — useful in test failure hooks.
Pull a field out of a REST Assured response body for use in a subsequent request — the standard pattern for chaining create → read → delete flows.
import static io.restassured.RestAssured.*;import static org.hamcrest.Matchers.*;import io.restassured.response.Response;@Testpublic void createOrder_thenFetchById() { // Step 1 — create a resource and extract its ID String orderId = given() .baseUri("https://api.example.com") .header("Authorization", "Bearer " + System.getenv("API_TOKEN")) .contentType("application/json") .body("{\"product\":\"Widget\",\"qty\":2}") .when() .post("/api/orders") .then() .statusCode(201) .extract().path("id"); // JsonPath extraction // Step 2 — use the extracted value in a follow-up request given() .baseUri("https://api.example.com") .header("Authorization", "Bearer " + System.getenv("API_TOKEN")) .when() .get("/api/orders/" + orderId) .then() .statusCode(200) .body("id", equalTo(orderId)) .body("status", equalTo("pending"));}
Chain auth token from login into subsequent requests
ApiJS
Store a JWT from a login response into a Postman collection variable, then reference it in every subsequent request's Authorization header — eliminates manual copy-paste between runs.
A consistent naming convention for test selectors keeps test code readable and prevents ID collisions.
// Pattern: [feature]-[component]-[action/state]// Use lowercase kebab-case, be specific but not brittle// ✅ Good<button data-testid="checkout-submit-btn">Place Order</button><input data-testid="search-query-input" /><div data-testid="product-card-list"> <div data-testid="product-card-item">...</div></div>// ✅ Dynamic IDs (include the record ID for uniqueness)<tr data-testid={`user-row-${user.id}`}> <td data-testid={`user-name-${user.id}`}>{user.name}</td></tr>// ✅ Add testId as an optional prop so components are always testabletype ButtonProps = { label: string; onClick: () => void; testId?: string;};function Button({ label, onClick, testId }: ButtonProps) { return ( <button data-testid={testId} onClick={onClick}> {label} </button> );}// ❌ Avoid: too generic, couples test to visual structure// data-testid="button"// data-testid="container"// data-testid="div1"
.gitignore for test projects
GeneralINI
A .gitignore covering Cypress, Playwright, Node, and common OS files for a typical test project.
# Cypresscypress/screenshots/cypress/videos/cypress/downloads/cypress/.nyc_output/# Playwrightplaywright-report/test-results/tests/.auth/*.zip# Nodenode_modules/.env.env.local.env.*.local# Test coveragecoverage/.nyc_output/lcov.info# Build outputdist/out/.next/build/# IDE.idea/.vscode/settings.json*.iml# OS.DS_StoreThumbs.dbdesktop.ini
Test naming convention (describe/it pattern)
GeneralTS
Well-named tests read as plain English sentences and make failure messages instantly clear without opening the code.
// Pattern: describe('[Feature/Component]') > it('[condition] [expected outcome]')// Test names should form a sentence: "LoginPage with valid credentials redirects to dashboard"describe('LoginPage', () => { describe('with valid credentials', () => { it('redirects to the dashboard', async () => { /* ... */ }); it('stores the session token in localStorage', async () => { /* ... */ }); }); describe('with invalid credentials', () => { it('displays an error message below the form', async () => { /* ... */ }); it('does not redirect away from the login page', async () => { /* ... */ }); it('clears the password field after submission', async () => { /* ... */ }); }); describe('when the server is unavailable', () => { it('shows a service unavailable message', async () => { /* ... */ }); it('includes a retry button', async () => { /* ... */ }); });});// ❌ Avoid vague names:// it('works correctly')// it('test login')// it('should work')
Deep clone objects containing Date, Map, or Set
GeneralJS
Use structuredClone for a true deep copy that preserves Date objects and collections — JSON.parse/stringify silently drops Dates, Maps, and Sets.
const original = { name: 'Alice', joined: new Date('2024-01-15'), roles: new Set(['admin', 'editor']), meta: new Map([['plan', 'pro'], ['region', 'eu']]), address: { city: 'Berlin', zip: '10115' },};// ✅ structuredClone — preserves Date, Set, Mapconst deep = structuredClone(original);deep.address.city = 'Munich'; // does not affect originalconsole.log(deep.joined instanceof Date); // trueconsole.log(deep.roles instanceof Set); // true// ❌ JSON round-trip — loses typesconst broken = JSON.parse(JSON.stringify(original));console.log(broken.joined instanceof Date); // false — it's a stringconsole.log(broken.roles); // undefined — Set is gone// structuredClone is available in Node 17+, all modern browsers.
Exhaustive switch with never for union types
GeneralTS
Use a never-typed fallthrough to make TypeScript enforce that every union member is handled — adding a new variant without updating the switch becomes a compile error.
type Status = 'pending' | 'running' | 'passed' | 'failed' | 'skipped';function assertNever(value: never, message?: string): never { throw new Error(message ?? `Unhandled variant: ${JSON.stringify(value)}`);}function statusLabel(status: Status): string { switch (status) { case 'pending': return '⏳ Pending'; case 'running': return '🔄 Running'; case 'passed': return '✅ Passed'; case 'failed': return '❌ Failed'; case 'skipped': return '⏭ Skipped'; default: // TypeScript errors here if a case is missing return assertNever(status, `Unknown status: ${status}`); }}// Adding 'cancelled' to Status without updating the switch// immediately produces: Argument of type 'string' is not assignable to// parameter of type 'never'
Debounce and throttle utility functions
GeneralJS
Paste-ready debounce (delay until quiet) and throttle (limit rate) implementations — useful in test utilities, search inputs, and scroll handlers without adding a library.
// Debounce: fires only after `ms` of silence.// Use for: search-as-you-type, resize handlers, autosave.function debounce(fn, ms) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), ms); };}// Throttle: fires at most once per `ms`.// Use for: scroll events, mousemove tracking, API polling.function throttle(fn, ms) { let last = 0; return (...args) => { const now = Date.now(); if (now - last >= ms) { last = now; fn(...args); } };}// Usageconst debouncedSearch = debounce((q) => fetchResults(q), 300);const throttledScroll = throttle(() => updateProgressBar(), 100);window.addEventListener('input', (e) => debouncedSearch(e.target.value));window.addEventListener('scroll', throttledScroll);
Type-safe environment variable accessor
GeneralTS
Wrap process.env access in a typed helper that throws at startup for missing required variables — surfaces misconfigured CI environments immediately rather than at runtime.
function requireEnv(key: string): string { const value = process.env[key]; if (!value) { throw new Error(`Missing required environment variable: ${key}`); } return value;}function optionalEnv(key: string, fallback: string): string { return process.env[key] ?? fallback;}// ── Config object (validated once at import time) ───────────────────────────export const config = { baseUrl: requireEnv('BASE_URL'), apiKey: requireEnv('API_KEY'), dbUrl: requireEnv('DATABASE_URL'), logLevel: optionalEnv('LOG_LEVEL', 'info'), maxRetries: Number(optionalEnv('MAX_RETRIES', '3')),} as const;// Usage in tests:// import { config } from './config';// await page.goto(config.baseUrl + '/login');
Remove merged branches locally and on remote
GeneralBash
Delete feature branches that have already been merged into main — safe to run weekly to keep your local and remote branch list tidy.
#!/usr/bin/env bash# Deletes branches merged into main, both locally and on origin.# Run from any branch; skips main, master, develop, and the current branch.set -euo pipefailMAIN="main" # change to 'master' if neededecho "Fetching and pruning remote refs..."git fetch --prune# Local merged branchesMERGED_LOCAL=$(git branch --merged "$MAIN" \ | grep -v -E "(^\*|\b(main|master|develop)\b)")if [ -z "$MERGED_LOCAL" ]; then echo "No local merged branches to delete."else echo "Deleting local branches:" echo "$MERGED_LOCAL" | xargs git branch -dfi# Remote merged branchesMERGED_REMOTE=$(git branch -r --merged "$MAIN" \ | grep 'origin/' \ | grep -v -E "(origin/(main|master|develop|HEAD))" \ | sed 's/origin\///')if [ -z "$MERGED_REMOTE" ]; then echo "No remote merged branches to delete."else echo "Deleting remote branches:" echo "$MERGED_REMOTE" | xargs -I{} git push origin --delete {}fiecho "Done."
Find commit that introduced a specific string
GeneralBash
Use git log -S to locate the exact commit that added or removed a function, variable, or string — far faster than manually grepping through history.
# Find commits that added or removed the exact string 'resetPassword'git log -S 'resetPassword' --oneline# With full diff to see what changedgit log -S 'resetPassword' --patch# Limit to a specific file or directorygit log -S 'resetPassword' -- src/auth/# -G searches by regex (matches lines containing the pattern)git log -G 'reset.*[Pp]assword' --oneline# Include all branchesgit log -S 'SECRET_KEY' --all --oneline# Narrowed to a date rangegit log -S 'db.connect' --after='2024-01-01' --before='2024-06-01' --oneline# Show author and date alongside each matchgit log -S 'TODO: remove before release' --format='%h %ad %an — %s' --date=short
Matrix strategy across multiple Node versions
GeneralYAML
Run your test suite in parallel across Node 18, 20, and 22 using a GitHub Actions matrix — catches version-specific breakage before it reaches production.
name: Test Matrixon: push: branches: [main] pull_request:jobs: test: name: Node ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false # keep running other versions if one fails matrix: node-version: [18, 20, 22] steps: - uses: actions/checkout@v4 - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci - run: npm test - name: Upload coverage if: matrix.node-version == 20 # only upload once uses: actions/upload-artifact@v4 with: name: coverage path: coverage/
Cache npm dependencies and Playwright browsers
GeneralYAML
Cache both node_modules and the Playwright browser binaries in GitHub Actions — cuts cold-start time from ~3 minutes to under 30 seconds on cache hit.