PracticeQAAutomation logoPracticeQAAutomation
Pw

TypeScript · @playwright/test

Help desk with Playwright

Customers raise tickets (priority set by category); agents work a queue with live SLA countdowns and breach states, bulk-assign, reply with canned responses and formatting, add internal notes, and resolve with a required reason.

App under test

🎧 DeskFlow

URL
/apps/helpdesk
Agent
agent@deskflow.dev
Password
Desk@123
SLA
Urgent 30m · High 1h · Med 4h · Low 24h

Scenarios to automate

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

  1. 1

    Derived priority

    Beginner

    Choosing ticket-category shows which priority the ticket will get (Technical → High).

  2. 2

    Live SLA timers

    Intermediate

    sla-<id> counts down each second; breached tickets have data-breached="true" and red text.

  3. 3

    Queue filters

    Beginner

    status-filter, priority-filter, owner-filter and queue-search combine; assert queue-count.

  4. 4

    Bulk assign

    Intermediate

    select-all-tickets → bulk-assign-me updates every assignee-<id> cell.

  5. 5

    Toolbar formatting

    Intermediate

    Select text in reply-text, click format-bold; the sent message renders <strong>.

  6. 6

    Internal notes

    Intermediate

    internal-note switch → message has data-internal="true" and doesn't count as a first response.

  7. 7

    First response

    Advanced

    Sending a public reply stops the SLA timer (“Responded”) and moves Open → Pending.

  8. 8

    Resolution rule

    Intermediate

    Setting ticket-status to Resolved without resolution-category shows ticket-error.

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
deskflow.spec.ts
import { test, expect } from '@playwright/test';

// playwright.config.ts → use: { baseURL: 'https://www.practiceqaautomation.com' }
// Locators in this suite: getByRole ×4 · getByLabel ×4 · CSS ×3 · XPath ×4 · getByTestId ×10
test('raises a ticket and resolves it as an agent', async ({ page }) => {
  await page.goto('/apps/helpdesk');
  await page.getByLabel('Your name').fill('Grace Lee');
  await page.locator('#requester-email').fill('grace@globex.com');
  await page.getByRole('combobox', { name: 'Category' }).selectOption('technical');
  await page.getByTestId('ticket-subject').fill('Cannot export report to PDF');
  await page.getByLabel('Description').fill('The export button spins forever and nothing downloads.');
  await page.locator('xpath=//button[normalize-space()="Submit ticket"]').click();
  await expect(page.getByTestId('ticket-created')).toContainText('#1041');

  // Switch to the agent side
  await page.getByTestId('agent-login-link').click();
  await page.locator('#agent-email').fill('agent@deskflow.dev');
  await page.getByLabel('Password').fill('Desk@123');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page).toHaveURL(/\/apps\/helpdesk\/queue/);
  await expect(page.getByTestId('open-ticket').nth(0)).toContainText('Cannot export report');
  await expect(page.getByTestId('sla-1041')).toContainText('00:5');
  await page.getByTestId('open-ticket').nth(0).click();
  await page.locator('xpath=//button[normalize-space()="Assign to me"]').click();
  await expect(page.getByTestId('ticket-toast')).toContainText('Assigned');
  await page.getByRole('combobox', { name: 'Insert canned response' }).selectOption('greeting');
  await page.getByTestId('send-reply').click();
  await expect(page.getByTestId('message').nth(1)).toContainText('thanks for reaching out');
  await page.getByRole('switch', { name: 'Internal note' }).click();
  await page.getByTestId('reply-text').fill('Reproduced on staging, escalating to dev.');
  await page.getByTestId('send-reply').click();
  await expect(page.getByTestId('message').nth(2)).toContainText('Reproduced on staging');
  await page.locator('#ticket-status').selectOption('Resolved');
  await page.locator('xpath=//button[normalize-space()="Update ticket"]').click();
  await expect(page.getByTestId('ticket-error')).toBeVisible();
  await page.getByLabel('Resolution').selectOption('bug-fixed');
  await page.locator('xpath=//button[normalize-space()="Update ticket"]').click();
  await expect(page.getByTestId('ticket-status-badge')).toContainText('Resolved');
});

Practicing another tool?