~/resources/snippetssection live
$ qa open snippets --framework all

//Snippets

Copy, paste, ship.

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 click
cy.get('[data-testid="submit-btn"]').click();

// Double-click
cy.get('[data-testid="editable-cell"]').dblclick();

// Right-click to open context menu
cy.get('[data-testid="context-item"]').rightclick();

// Click inside a scoped area
cy.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 type
cy.get('[data-testid="email-input"]').clear().type('user@example.com');

// Submit form by pressing Enter
cy.get('[data-testid="search-input"]').type('test query{enter}');

// Tab between fields
cy.get('[data-testid="first-name"]').type('Alice{tab}');
cy.get('[data-testid="last-name"]').type('Smith');

// Slow typing to simulate a real user
cy.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.

// Exact match
cy.get('[data-testid="welcome-heading"]').should('have.text', 'Welcome, Alice!');

// Trimmed match (ignores leading/trailing whitespace)
cy.get('[data-testid="status-badge"]').invoke('text').invoke('trim').should('eq', 'Active');

// Chained assertion
cy.get('[data-testid="total-price"]')
  .should('be.visible')
  .and('have.text', '£29.99');

Assert text content contains a substring

CypressTS

Check that an element's text includes a specific substring — useful for dynamic or lengthy text.

// Partial match
cy.get('[data-testid="error-message"]').should('contain.text', 'Invalid email');

// Case-insensitive check using invoke
cy.get('[data-testid="notification"]')
  .invoke('text')
  .then((text) => {
    expect(text.toLowerCase()).to.include('success');
  });

// Multiple assertions in one chain
cy.get('[data-testid="order-summary"]')
  .should('contain.text', 'Order #')
  .and('contain.text', 'Processing');

Intercept and stub a GET request

CypressTS

Replace a real API response with fixture data so tests run deterministically without a live backend.

// Stub with a fixture file (cypress/fixtures/users.json)
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
cy.get('[data-testid="user-list"]').should('exist');

// Stub with inline response
cy.intercept('GET', '/api/users/*', {
  statusCode: 200,
  body: { id: '1', name: 'Alice', email: 'alice@example.com' },
}).as('getUser');

// Stub an error state
cy.intercept('GET', '/api/products', {
  statusCode: 503,
  body: { message: 'Service temporarily unavailable' },
}).as('serverError');

Tip: Register the intercept before cy.visit() — Cypress needs the route registered before the request fires.

Intercept and stub a POST request

CypressTS

Mock POST responses to test success, validation error, and server error states without hitting a real API.

// Stub a successful login
cy.intercept('POST', '/api/auth/login', {
  statusCode: 200,
  body: { token: 'fake-jwt-token', user: { id: '1', name: 'Alice' } },
}).as('login');

// Stub a validation error
cy.intercept('POST', '/api/auth/login', {
  statusCode: 422,
  body: { error: 'Invalid email or password' },
}).as('loginError');

// Intercept and inspect the request body
cy.intercept('POST', '/api/orders', (req) => {
  expect(req.body.items).to.have.length.greaterThan(0);
  req.reply({ statusCode: 201, body: { orderId: 'ORD-001' } });
}).as('createOrder');

Wait for an aliased network request

CypressTS

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 response
cy.wait('@loadStats').then((interception) => {
  expect(interception.response?.statusCode).to.equal(200);
  expect(interception.response?.body).to.have.property('totalUsers');
});

// Shorthand status assertion
cy.wait('@loadStats').its('response.statusCode').should('equal', 200);

// Wait for multiple requests at once
cy.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 directory
cy.get('input[type="file"]').selectFile('cypress/fixtures/sample-report.pdf');

// Upload multiple files
cy.get('input[type="file"]').selectFile([
  'cypress/fixtures/screenshot-1.png',
  'cypress/fixtures/screenshot-2.png',
]);

// Simulate drag-and-drop onto a dropzone
cy.get('[data-testid="dropzone"]').selectFile(
  'cypress/fixtures/test-data.csv',
  { action: 'drag-drop' }
);

// Assert the uploaded file name appears
cy.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 file
beforeEach(() => {
  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.

// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
    }
  }
}

Cypress.Commands.add('login', (email: string, password: string) => {
  cy.request({
    method: 'POST',
    url: '/api/auth/login',
    body: { email, password },
  }).then(({ body }) => {
    window.localStorage.setItem('authToken', body.token);
  });
});

// Usage in any spec file
beforeEach(() => {
  cy.login('alice@example.com', 'password123');
});

Assert URL after navigation or redirect

CypressTS

Verify the browser is on the expected URL after a login redirect, form submission, or link click.

// Exact URL match
cy.url().should('equal', 'https://app.example.com/dashboard');

// Partial match (good for dynamic IDs in the path)
cy.url().should('include', '/dashboard');

// Regex match
cy.url().should('match', /\/orders\/[a-z0-9-]+\/confirmation/);

// After clicking a link
cy.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 count
cy.get('[data-testid="product-card"]').should('have.length', 12);

// At least one result
cy.get('.search-result').should('have.length.greaterThan', 0);

// No results
cy.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 preset
cy.viewport('iphone-14');
cy.viewport('ipad-2');

// Use explicit dimensions (width, height)
cy.viewport(375, 812);  // iPhone SE
cy.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 spec
const 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 it
cy.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);
});

// Usage
cy.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 display
cy.get('[data-testid="tooltip"]')
  .should('have.css', 'opacity', '0');

// Assert font size
cy.get('[data-testid="price"]')
  .should('have.css', 'font-size', '24px');

// Assert using invoke for computed values
cy.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 row
cy.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 count
cy.get('table tbody tr').should('have.length', 10);

// Find a row by cell content
cy.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 call
const 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 screenshot
cy.screenshot('login-page-before-submit');

// Screenshot of a specific element only
cy.get('[data-testid="error-summary"]').screenshot('validation-errors');

// Screenshot with options
cy.screenshot('dashboard-state', {
  capture: 'fullPage',
  overwrite: true,
});

// In a custom command — automatic screenshot on failure
Cypress.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.ts
import { 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 test
const 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'); // macOS
cy.get('body').type('{ctrl}k'); // Windows/Linux

// Close a modal with Escape
cy.get('[data-testid="modal"]').should('be.visible');
cy.get('body').type('{esc}');
cy.get('[data-testid="modal"]').should('not.exist');

// Select all and delete
cy.get('[data-testid="editor"]').focus().type('{selectAll}{del}');

// Ctrl+Z undo
cy.get('[data-testid="editor"]').focus().type('{ctrl}z');

// Arrow key navigation
cy.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 loads
cy.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 visit
cy.window().then((win) => {
  win.localStorage.setItem('theme', 'dark');
});

// Assert a value was set
cy.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 request
cy.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 endpoint
cy.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 count
cy.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 name
await page.getByRole('button', { name: 'Submit' }).click();

// Click a link
await page.getByRole('link', { name: 'Sign in' }).click();

// Click a menu item
await page.getByRole('menuitem', { name: 'Delete account' }).click();

// Exact vs partial name match
await page.getByRole('button', { name: 'Save changes', exact: true }).click();

// Click a checkbox
await page.getByRole('checkbox', { name: 'Accept terms' }).check();

// Click inside a specific region
await 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 match
await expect(page.getByTestId('status-badge')).toHaveText('Active');

// Partial match
await expect(page.getByTestId('notification')).toContainText('updated successfully');

// Regex match
await expect(page.getByTestId('invoice-number')).toHaveText(/INV-\d+/);

// Assert text on all matching elements
await expect(page.getByRole('listitem')).toHaveText(['Apple', 'Banana', 'Cherry']);

// Read text into a variable
const 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.

import { test, expect } from '@playwright/test';

test('shows users from API', async ({ page }) => {
  // Intercept before navigating
  await page.route('**/api/users', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: '1', name: 'Alice', email: 'alice@example.com' },
      ]),
    });
  });

  await page.goto('/users');
  await expect(page.getByText('Alice')).toBeVisible();
});

test('handles server error gracefully', async ({ page }) => {
  await page.route('**/api/users', (route) =>
    route.fulfill({ status: 500, body: 'Internal Server Error' })
  );
  await page.goto('/users');
  await expect(page.getByTestId('error-message')).toBeVisible();
});

Tip: Register routes before page.goto() — routes set after navigation won't catch requests that already fired.

Wait for a specific network response

PlaywrightTS

Pause execution until a network request completes, then assert on the response — avoids timing-based waits.

import { test, expect } from '@playwright/test';

test('loads products after clicking refresh', async ({ page }) => {
  await page.goto('/products');

  // Wait for a response triggered by an action
  const [response] = await Promise.all([
    page.waitForResponse('**/api/products'),
    page.getByRole('button', { name: 'Refresh' }).click(),
  ]);

  expect(response.status()).toBe(200);
  const data = await response.json();
  expect(data.items).toHaveLength(10);

  // Wait for a response matching a predicate
  const searchResponse = await Promise.all([
    page.waitForResponse((res) => res.url().includes('/search') && res.status() === 200),
    page.getByLabel('Search').fill('widget'),
  ]);

Upload a file

PlaywrightTS

Set files on a file input element — supports single files, multiple files, and programmatic Buffer objects.

import { test, expect } from '@playwright/test';
import * as path from 'path';

test('upload a document', async ({ page }) => {
  await page.goto('/upload');

  // Upload via a visible file input
  await page.getByLabel('Upload document').setInputFiles(
    path.join(__dirname, 'fixtures/sample.pdf')
  );

  // Upload multiple files
  await page.locator('input[type="file"]').setInputFiles([
    'tests/fixtures/image-1.png',
    'tests/fixtures/image-2.png',
  ]);

  // Clear a file selection
  await page.locator('input[type="file"]').setInputFiles([]);

  await expect(page.getByTestId('upload-status')).toContainText('Ready to upload');
  await page.getByRole('button', { name: 'Submit' }).click();
  await expect(page.getByTestId('success-message')).toBeVisible();
});

Reuse login state with storageState

PlaywrightTS

Authenticate once via API, save the browser storage, then reuse across all tests — eliminates repeated login UI flows.

// tests/global.setup.ts
import { chromium } from '@playwright/test';

async function globalSetup() {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  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 page.waitForURL('**/dashboard');

  // Save auth state to disk
  await context.storageState({ path: 'tests/.auth/user.json' });
  await browser.close();
}

export default globalSetup;

// playwright.config.ts
// export default defineConfig({
//   globalSetup: './tests/global.setup.ts',
//   use: { storageState: 'tests/.auth/user.json' },
// });

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 match
await expect(page).toHaveURL('https://app.example.com/dashboard');

// Partial match (contains a substring)
await expect(page).toHaveURL(/\/dashboard/);

// After a form submission redirect
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page).toHaveURL(/.*\/dashboard/, { timeout: 5000 });

// After clicking a link
await 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 count
await expect(page.getByTestId('product-card')).toHaveCount(12);

// No results
await expect(page.getByRole('row', { name: /invoice/ })).toHaveCount(0);

// Count manually
const items = page.getByRole('listitem');
const count = await items.count();
expect(count).toBeGreaterThan(0);

// Assert each item has text
for (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 screenshot
await page.screenshot({ path: 'screenshots/full-page.png', fullPage: true });

// Viewport screenshot only
await page.screenshot({ path: 'screenshots/viewport.png' });

// Screenshot of a single element
await 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.ts
import { 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 text
const 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 array
const labels = await page.getByRole('listitem').allTextContents();
console.log(labels); // ['Item 1', 'Item 2', 'Item 3']

// Assert after reading
const 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.

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: [['html', { open: 'never' }]],

  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

  projects: [
    { name: 'Desktop Chrome', use: { ...devices['Desktop Chrome'] } },
    { name: 'Desktop Firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'Mobile Safari',  use: { ...devices['iPhone 14'] } },
    { name: 'Tablet',         use: { viewport: { width: 768, height: 1024 } } },
  ],
});

Capture and assert browser console errors

PlaywrightTS

Collect all browser console errors during a test and assert none were logged — catches uncaught JS exceptions that don't fail the test by default.

import { test, expect } from '@playwright/test';

test('page loads without console errors', async ({ page }) => {
  const errors: string[] = [];

  page.on('console', (msg) => {
    if (msg.type() === 'error') errors.push(msg.text());
  });

  page.on('pageerror', (err) => {
    errors.push(`Uncaught: ${err.message}`);
  });

  await page.goto('/dashboard');
  await page.getByRole('button', { name: 'Load data' }).click();
  await page.waitForLoadState('networkidle');

  expect(errors, `Console errors: ${errors.join(', ')}`).toHaveLength(0);
});

Block third-party scripts and trackers

PlaywrightTS

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.

import { test, expect } from '@playwright/test';

const BLOCKED_DOMAINS = [
  'google-analytics.com',
  'googletagmanager.com',
  'doubleclick.net',
  'facebook.net',
  'hotjar.com',
  'segment.io',
];

test('checkout page loads without trackers', async ({ page }) => {
  await page.route('**/*', (route) => {
    const url = route.request().url();
    const isBlocked = BLOCKED_DOMAINS.some((d) => url.includes(d));
    if (isBlocked) {
      route.abort();
    } else {
      route.continue();
    }
  });

  await page.goto('/checkout');
  await expect(page.getByRole('heading', { name: 'Checkout' })).toBeVisible();
});

Run axe accessibility scan and report violations

PlaywrightTS

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 webdriver
from selenium.webdriver.chrome.options import Options

def 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 pytest
import pytest

@pytest.fixture
def 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 cases
WebElement submitBtn = driver.findElement(By.cssSelector("[data-testid='submit-btn']"));
submitBtn.click();

// By ID
WebElement email = driver.findElement(By.id("email-input"));
email.sendKeys("user@example.com");

// By class name
WebElement card = driver.findElement(By.className("product-card"));

// XPath — use when CSS can't target by text
WebElement 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 rows
List<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 0
for (WebElement row : rows) {
    String name = row.findElement(By.cssSelector("td:first-child")).getText();
    System.out.println(name);
}

// Find a row containing specific text
WebElement 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 clickable
WebElement button = wait.until(
    ExpectedConditions.elementToBeClickable(By.cssSelector("[data-testid='submit']"))
);
button.click();

// Wait for URL to include a path segment
wait.until(ExpectedConditions.urlContains("/dashboard"));

// Wait for text to appear
wait.until(ExpectedConditions.textToBePresentInElementLocated(
    By.id("status-message"), "Saved successfully"
));

// Wait for element to disappear
wait.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 By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, timeout=10)

# Wait until element is clickable
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "[data-testid='submit']")))
button.click()

# Wait for URL
wait.until(EC.url_contains('/dashboard'))

# Wait for text to appear
wait.until(EC.text_to_be_present_in_element((By.ID, 'status'), 'Saved'))

# Wait for element to disappear
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, '.spinner')))

# Wait for element to be present in DOM
element = 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 menu
WebElement navMenu = driver.findElement(By.cssSelector("[data-testid='products-menu']"));
actions.moveToElement(navMenu).perform();

// Hover then click a revealed sub-item
WebElement subItem = driver.findElement(By.cssSelector("[data-testid='submenu-electronics']"));
actions.moveToElement(navMenu)
       .moveToElement(subItem)
       .click()
       .perform();

// Hover to reveal tooltip
WebElement 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 ActionChains
from selenium.webdriver.common.by import By

source = driver.find_element(By.CSS_SELECTOR, "[data-testid='drag-item']")
target = driver.find_element(By.CSS_SELECTOR, "[data-testid='drop-zone']")

# Simple drag and drop
ActionChains(driver).drag_and_drop(source, target).perform()

# Step-by-step for more control
ActionChains(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 text
dropdown.selectByVisibleText("United Kingdom");

// Select by value attribute
dropdown.selectByValue("gb");

// Select by index (0-based)
dropdown.selectByIndex(2);

// Read the selected value
String selected = dropdown.getFirstSelectedOption().getText();
System.out.println("Selected: " + selected);

// Multi-select: get all selected options
List<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 element
WebElement frame = driver.findElement(By.cssSelector("iframe[data-testid='payment-frame']"));
driver.switchTo().frame(frame);

// Interact with elements inside the iframe
driver.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 document
driver.switchTo().defaultContent();

// Back in the main page
driver.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 handle
String originalWindow = driver.getWindowHandle();

// Click a link that opens a new window/tab
driver.findElement(By.cssSelector("[data-testid='open-report-link']")).click();

// Wait for the new window and switch to it
for (String handle : driver.getWindowHandles()) {
    if (!handle.equals(originalWindow)) {
        driver.switchTo().window(handle);
        break;
    }
}

// Interact with the new window
assertTrue(driver.getTitle().contains("Report"));

// Close the new window and switch back
driver.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.

import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebElement;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

// Full page screenshot
byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
Files.write(Paths.get("screenshots/failure.png"), screenshot);

// Element-level screenshot (Selenium 4+)
WebElement card = driver.findElement(By.cssSelector("[data-testid='summary-card']"));
File elementShot = card.getScreenshotAs(OutputType.FILE);
Files.copy(elementShot.toPath(), Paths.get("screenshots/summary-card.png"));

Page Object Model (Java)

SeleniumJava

A base page class and a concrete LoginPage using the POM pattern — keeps locators out of test code.

// BasePage.java
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.*;
import java.time.Duration;

public abstract class BasePage {
    protected final WebDriver driver;
    protected final WebDriverWait wait;

    protected BasePage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    protected WebElement waitForClickable(By locator) {
        return wait.until(ExpectedConditions.elementToBeClickable(locator));
    }
}

// LoginPage.java
public class LoginPage extends BasePage {
    private final By emailField   = By.cssSelector("[data-testid='email']");
    private final By passwordField = By.cssSelector("[data-testid='password']");
    private final By submitButton  = By.cssSelector("[data-testid='submit']");
    private final By errorMessage  = By.cssSelector("[data-testid='error-msg']");

    public LoginPage(WebDriver driver) {
        super(driver);
        driver.get("/login");
    }

    public DashboardPage loginAs(String email, String password) {
        waitForClickable(emailField).sendKeys(email);
        driver.findElement(passwordField).sendKeys(password);
        waitForClickable(submitButton).click();
        return new DashboardPage(driver);
    }

    public String getErrorMessage() {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(errorMessage)).getText();
    }
}

Common Selenium assertions in Python

SeleniumPy

Reference card for the most-used Selenium assertion patterns in Python — element visibility, text, attributes, counts, and URL checks with pytest.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def test_dashboard_assertions(driver):
    driver.get('https://app.example.com/dashboard')
    wait = WebDriverWait(driver, 10)

    # Element is visible
    header = wait.until(EC.visibility_of_element_located((By.TAG_NAME, 'h1')))
    assert header.is_displayed()

    # Text content
    assert header.text == 'Dashboard'

    # Attribute value
    logo = driver.find_element(By.CSS_SELECTOR, 'img.logo')
    assert 'qa-codes' in logo.get_attribute('alt')

    # Element count
    cards = driver.find_elements(By.CSS_SELECTOR, '[data-testid="stat-card"]')
    assert len(cards) == 4

    # URL check
    assert '/dashboard' in driver.current_url

    # Element NOT present
    error_els = driver.find_elements(By.CSS_SELECTOR, '.error-banner')
    assert len(error_els) == 0, 'Unexpected error banner visible'

// 🔌 API · 11 snippets

cy.request — GET with assertions

ApiTS

Make a real HTTP GET request from a Cypress test and assert on status code, headers, and body.

cy.request({
  method: 'GET',
  url: '/api/users',
  headers: { Authorization: `Bearer ${Cypress.env('apiToken')}` },
}).then((response) => {
  expect(response.status).to.equal(200);
  expect(response.headers['content-type']).to.include('application/json');
  expect(response.body).to.have.property('users');
  expect(response.body.users).to.be.an('array').with.length.greaterThan(0);

  const firstUser = response.body.users[0];
  expect(firstUser).to.include.keys('id', 'email', 'role');
  expect(firstUser.email).to.match(/.+@.+\..+/);
});

cy.request — POST with body

ApiTS

Create a resource via the API and assert the response — useful for test data setup.

cy.request({
  method: 'POST',
  url: '/api/products',
  headers: { Authorization: `Bearer ${Cypress.env('apiToken')}` },
  body: {
    name: 'Widget Pro',
    price: 29.99,
    sku: 'WGT-001',
    inStock: true,
  },
}).then((response) => {
  expect(response.status).to.equal(201);
  expect(response.body.id).to.be.a('string');
  expect(response.body.name).to.equal('Widget Pro');
  expect(response.body.price).to.equal(29.99);

  // Store the created ID for later test steps
  cy.wrap(response.body.id).as('newProductId');
});

Playwright — authenticated POST request

ApiTS

Send an authenticated POST and assert the response status and shape using Playwright's request fixture.

import { test, expect } from '@playwright/test';

test('POST /api/products creates a product', async ({ request }) => {
  const response = await request.post('/api/products', {
    data: {
      name: 'Widget Pro',
      price: 29.99,
      sku: 'WGT-001',
    },
    headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
  });

  expect(response.status()).toBe(201);

  const product = await response.json();
  expect(product.id).toBeTruthy();
  expect(product.name).toBe('Widget Pro');
  expect(product.price).toBe(29.99);

  // Clean up: delete the created resource
  await request.delete(`/api/products/${product.id}`, {
    headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
  });
});

Full CRUD lifecycle test

ApiTS

Create, read, update, and delete a resource in a single test — verifies the complete API lifecycle.

import { test, expect } from '@playwright/test';

test('CRUD lifecycle for a user', async ({ request }) => {
  const headers = { Authorization: `Bearer ${process.env.API_TOKEN}` };
  const email = `test+${Date.now()}@example.com`;

  // Create
  const created = await request.post('/api/users', {
    data: { name: 'Test User', email, role: 'viewer' },
    headers,
  });
  expect(created.status()).toBe(201);
  const { id } = await created.json();

  // Read
  const fetched = await request.get(`/api/users/${id}`, { headers });
  expect(fetched.status()).toBe(200);
  expect((await fetched.json()).email).toBe(email);

  // Update
  const updated = await request.patch(`/api/users/${id}`, {
    data: { role: 'admin' },
    headers,
  });
  expect(updated.status()).toBe(200);
  expect((await updated.json()).role).toBe('admin');

  // Delete
  const deleted = await request.delete(`/api/users/${id}`, { headers });
  expect(deleted.status()).toBe(204);

  // Verify gone
  const gone = await request.get(`/api/users/${id}`, { headers });
  expect(gone.status()).toBe(404);
});

JWT auth: get token, then call protected endpoint

ApiTS

Authenticate via a login endpoint to get a JWT, then use it to call protected resources.

import { test, expect } from '@playwright/test';

test('auth flow: login then access /me', async ({ request }) => {
  // Step 1: get a token
  const loginRes = await request.post('/api/auth/login', {
    data: { email: 'user@example.com', password: 'password123' },
  });
  expect(loginRes.status()).toBe(200);
  const { token } = await loginRes.json();
  expect(token).toBeTruthy();

  // Step 2: call a protected endpoint
  const meRes = await request.get('/api/me', {
    headers: { Authorization: `Bearer ${token}` },
  });
  expect(meRes.status()).toBe(200);
  const me = await meRes.json();
  expect(me.email).toBe('user@example.com');

  // Step 3: verify expired token is rejected
  const expiredRes = await request.get('/api/me', {
    headers: { Authorization: 'Bearer expired.token.here' },
  });
  expect(expiredRes.status()).toBe(401);
});

Response schema validation with Zod

ApiTS

Parse and validate the API response body against a Zod schema — catches unexpected field changes or missing keys.

import { z } from 'zod';
import { test, expect } from '@playwright/test';

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
  email: z.string().email(),
  role: z.enum(['admin', 'viewer', 'editor']),
  createdAt: z.string().datetime(),
});

const UsersResponseSchema = z.object({
  users: z.array(UserSchema),
  total: z.number().int().nonneg(),
  page: z.number().int().positive(),
});

test('GET /api/users response matches schema', async ({ request }) => {
  const res = await request.get('/api/users');
  expect(res.status()).toBe(200);

  const body = await res.json();
  const result = UsersResponseSchema.safeParse(body);
  expect(
    result.success,
    `Schema mismatch: ${JSON.stringify(result.error?.issues)}`
  ).toBe(true);
});

Tip: Install Zod: npm install zod. Zod errors include the exact field path and expected type, making failures easy to diagnose.

File upload via multipart/form-data (Playwright)

ApiTS

Upload a file to an API endpoint using multipart form data — no browser needed.

import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';

test('upload CSV file via API', async ({ request }) => {
  const fileBuffer = fs.readFileSync(
    path.resolve(__dirname, 'fixtures/test-data.csv')
  );

  const response = await request.post('/api/imports', {
    multipart: {
      file: {
        name: 'test-data.csv',
        mimeType: 'text/csv',
        buffer: fileBuffer,
      },
      type: 'users',
      dryRun: 'false',
    },
    headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
  });

  expect(response.status()).toBe(202);
  const { jobId } = await response.json();
  expect(jobId).toBeTruthy();
});

REST Assured — GET and POST (Java)

ApiJava

Make and assert HTTP requests in Java using REST Assured's fluent BDD-style DSL.

import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
import io.restassured.http.ContentType;

// GET request with assertions
@Test
public void getUsers_returns200WithUsersList() {
    given()
        .baseUri("https://api.example.com")
        .header("Authorization", "Bearer " + System.getenv("API_TOKEN"))
    .when()
        .get("/api/users")
    .then()
        .statusCode(200)
        .contentType(ContentType.JSON)
        .body("users", not(empty()))
        .body("users[0].email", containsString("@"));
}

// POST request with body
@Test
public void createProduct_returns201WithId() {
    String body = "{\"name\":\"Widget Pro\",\"price\":29.99}";

    given()
        .baseUri("https://api.example.com")
        .header("Authorization", "Bearer " + System.getenv("API_TOKEN"))
        .contentType(ContentType.JSON)
        .body(body)
    .when()
        .post("/api/products")
    .then()
        .statusCode(201)
        .body("id", notNullValue());
}

GraphQL query (Playwright)

ApiTS

Send a GraphQL query using Playwright's request context and assert on the data shape.

import { test, expect } from '@playwright/test';

test('GraphQL — query users list', async ({ request }) => {
  const response = await request.post('/graphql', {
    data: {
      query: `
        query GetUsers($limit: Int!) {
          users(limit: $limit) {
            id
            name
            email
            role
          }
        }
      `,
      variables: { limit: 10 },
    },
    headers: {
      'Authorization': `Bearer ${process.env.API_TOKEN}`,
      'Content-Type': 'application/json',
    },
  });

  expect(response.status()).toBe(200);
  const { data, errors } = await response.json();

  expect(errors).toBeUndefined();
  expect(data.users).toHaveLength(10);
  expect(data.users[0]).toMatchObject({
    id: expect.any(String),
    email: expect.stringContaining('@'),
  });
});

Extract JSON response value into a variable

ApiJava

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;

@Test
public 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.

// ── Login request — Tests tab ───────────────────────────────────────────────
// POST {{baseUrl}}/auth/login
// Body: { "email": "{{userEmail}}", "password": "{{userPassword}}" }

const res = pm.response.json();

if (pm.response.code === 200 && res.token) {
    pm.collectionVariables.set('authToken', res.token);
    pm.collectionVariables.set('tokenExpiry', Date.now() + 3600 * 1000);
    console.log('Token stored, expires in 1 hour');
} else {
    pm.test('Login succeeded', () => pm.expect(pm.response.code).to.equal(200));
}

// ── Pre-request script on the collection (auto-refresh) ─────────────────────
const expiry = pm.collectionVariables.get('tokenExpiry');
const isExpired = !expiry || Date.now() > Number(expiry);

if (isExpired) {
    pm.sendRequest({
        url: pm.collectionVariables.get('baseUrl') + '/auth/login',
        method: 'POST',
        header: { 'Content-Type': 'application/json' },
        body: {
            mode: 'raw',
            raw: JSON.stringify({
                email: pm.collectionVariables.get('userEmail'),
                password: pm.collectionVariables.get('userPassword'),
            }),
        },
    }, (err, res) => {
        if (!err) pm.collectionVariables.set('authToken', res.json().token);
    });
}

// ── Every authenticated request — Authorization header ──────────────────────
// Type: Bearer Token → Token: {{authToken}}

// 📦 General · 11 snippets

data-testid naming convention

GeneralTSX

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 testable
type 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.

# Cypress
cypress/screenshots/
cypress/videos/
cypress/downloads/
cypress/.nyc_output/

# Playwright
playwright-report/
test-results/
tests/.auth/
*.zip

# Node
node_modules/
.env
.env.local
.env.*.local

# Test coverage
coverage/
.nyc_output/
lcov.info

# Build output
dist/
out/
.next/
build/

# IDE
.idea/
.vscode/settings.json
*.iml

# OS
.DS_Store
Thumbs.db
desktop.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, Map
const deep = structuredClone(original);
deep.address.city = 'Munich';           // does not affect original
console.log(deep.joined instanceof Date); // true
console.log(deep.roles instanceof Set);  // true

// ❌ JSON round-trip — loses types
const broken = JSON.parse(JSON.stringify(original));
console.log(broken.joined instanceof Date); // false — it's a string
console.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);
    }
  };
}

// Usage
const 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 pipefail

MAIN="main"  # change to 'master' if needed

echo "Fetching and pruning remote refs..."
git fetch --prune

# Local merged branches
MERGED_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 -d
fi

# Remote merged branches
MERGED_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 {}
fi

echo "Done."

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 Matrix

on:
  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.

name: Playwright Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm           # built-in npm cache via setup-node

      - name: Install dependencies
        run: npm ci

      # Cache Playwright browser binaries (~300 MB)
      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

      - name: Install Playwright browsers
        if: steps.playwright-cache.outputs.cache-hit != 'true'
        run: npx playwright install --with-deps

      - name: Install system deps only (cache hit)
        if: steps.playwright-cache.outputs.cache-hit == 'true'
        run: npx playwright install-deps

      - name: Run Playwright tests
        run: npx playwright test

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/