PracticeQAAutomation logoPracticeQAAutomation
Pw

TypeScript · @playwright/test

CRM with Playwright

A leads table with filters, select-all and bulk assign, a new-lead modal with duplicate checks, an activity log, convert-to-deal, and a Kanban pipeline you can drag cards across.

App under test

📈 SalesPulse

URL
/apps/crm
Email
sales@crm.dev
Password
Crm@123
Stages
Qualified → Won/Lost

Scenarios to automate

Work top to bottom — each one adds a new technique.

  1. 1

    Table filters

    Beginner

    Combine lead-search, lead-status-filter and lead-source-filter; assert lead-count and rows.

  2. 2

    Select all & bulk assign

    Intermediate

    select-all-leads ticks every visible row; bulk-owner + apply-bulk updates owner-<id> cells.

  3. 3

    New lead validation

    Intermediate

    save-lead with an existing email shows “already exists”; bad phone/value show error-*.

  4. 4

    Activity log

    Intermediate

    Switch activity-note/call/email, save, and assert the newest activity-item text.

  5. 5

    Convert to deal

    Advanced

    convert-lead → deal-value + deal-close → lead-status becomes Converted and converted-banner appears.

  6. 6

    Kanban via menu

    Intermediate

    Change move-deal-<company> and verify the card is inside stage-<id> and stage-total-* updates.

  7. 7

    Kanban drag & drop

    Advanced

    Drag a deal-card onto another stage-* column (HTML5 DnD — dispatch events in Selenium/Cypress).

  8. 8

    Dashboard KPIs

    Beginner

    Winning a deal changes kpi-won and kpi-win-rate on the dashboard.

Sample Playwright suite

A working starting point that finds elements every way Playwright can — by role, label, text, id, name, CSS, XPath and data-testid — so you practice them all. Pick your language, switch to data-testid only to compare, or try your own in the Locator Playground.

Language
salespulse.spec.ts
import { test, expect } from '@playwright/test';

// playwright.config.ts → use: { baseURL: 'https://www.practiceqaautomation.com' }
// Locators in this suite: getByRole ×5 · getByLabel ×4 · CSS ×3 · XPath ×4 · getByTestId ×13
test('adds a lead and moves the deal through the pipeline', async ({ page }) => {
  await page.goto('/apps/crm');
  await page.getByLabel('Work email').fill('sales@crm.dev');
  await page.locator('#crm-password').fill('Crm@123');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page).toHaveURL(/\/apps\/crm\/dashboard/);
  await page.locator('xpath=//a[normalize-space()="Leads"]').click();
  await page.getByTestId('new-lead').click();
  await page.getByLabel('Contact name').fill('Grace Lee');
  await page.locator('#lead-company').fill('Globex Corp');
  await page.getByTestId('lead-email').fill('grace@globex.com');
  await page.getByLabel('Phone').fill('5550142233');
  await page.locator('#lead-source').selectOption('linkedin');
  await page.getByTestId('lead-value').fill('45000');
  await page.getByRole('button', { name: 'Save lead' }).click();
  await expect(page.getByTestId('leads-toast')).toContainText('created');
  await expect(page.getByTestId('leads-table')).toContainText('Globex Corp');

  // Newest lead is listed first
  await page.getByTestId('open-lead').nth(0).click();
  await expect(page.getByTestId('lead-title')).toContainText('Grace Lee');
  await page.locator('xpath=//button[normalize-space()="Log call"]').click();
  await page.getByTestId('activity-text').fill('Intro call — interested in the annual plan');
  await page.getByRole('button', { name: 'Save call' }).click();
  await expect(page.getByTestId('activity-timeline')).toContainText('annual plan');
  await page.locator('xpath=//button[normalize-space()="Convert to deal"]').click();
  await page.getByLabel('Deal value (USD)').fill('45000');
  await page.getByRole('combobox', { name: 'Expected close' }).selectOption('30');
  await page.locator('xpath=//button[normalize-space()="Create deal"]').click();
  await expect(page.getByTestId('lead-status')).toContainText('Converted');
  await page.getByTestId('view-pipeline').click();
  await expect(page.getByTestId('stage-qualified')).toContainText('Globex Corp');
  await page.getByRole('combobox', { name: 'Move Globex Corp' }).selectOption('proposal');
  await expect(page.getByTestId('stage-proposal')).toContainText('Globex Corp');
  await expect(page.getByTestId('stage-total-proposal')).toContainText('$212,000');
});

Practicing another tool?