VALIDATION LABMULTI SELECT

Selection

Multi Select

Select several skills, verify chips render in selection order, and assert removal via chip × button.

Intermediate

Scenario

Multi-selects show up in tag pickers, role assignment, country filters. Common bugs: order changes on removal, the same item appears twice, the form posts a comma-string instead of an array.

Skills

No skills picked yet.

Live widget · interact freely

Manual test checklist

  • 1Select three skills and confirm chips appear in the order picked, not alphabetically
  • 2Remove the middle chip — confirm the surrounding chips don't reorder
  • 3Re-add the same skill — does the chip return to its original spot or the end?
  • 4Use Backspace in the input area — does it remove the last chip?
  • 5Submit and inspect the network request — is the value an array, not a CSV string?

Expected result

Picking three skills produces three chips; removing the middle chip leaves two in their original order.

Automation challenge

Pick 3 chips, assert `count = 3` via `[data-testid^=ms-chip-]`. Remove the middle one, then assert the two remaining chips are in their original order — `expect(chips.first()).toHaveText('Cypress')`.

Stable selectors

  • Skill option Cypress[data-testid="ms-cypress"]
  • Skill option Playwright[data-testid="ms-playwright"]
  • Skill option K6[data-testid="ms-k6"]
  • Selected chip Playwright[data-testid="ms-chip-playwright"]
  • Remove chip Cypress[data-testid="ms-remove-cypress"]

Locator strategy

Three levels from simple IDs to scoped Playwright locators. IDs and names are easy to learn but are not always the best long-term choice when labels change or components repeat.

Use simple IDs and names to understand how locating elements works.

1
2
3
await page.goto('https://lab.hakdogan.com/practice/multi-select');

await page.locator('#ms-playwright').click();

Avoid as primary strategies

XPath (unless there is no alternative), long CSS chains, Tailwind-style utility class selectors, generated or unstable IDs, and volatile framework internals break when layout, styling, or DOM structure shifts.

1
2
await page.locator('.w-full.rounded-xl.border.bg-blue-500').fill('demo@example.com');
await page.locator('//div[2]/form/div[1]/input').fill('demo@example.com');

Reference Playwright spec

multi-select.spec.ts
ts
1
2
3
4
5
6
7
8
9
10
11
12
13
import { test, expect } from '@playwright/test';

test('multi-select chips behave correctly', async ({ page }) => {
  await page.goto('https://lab.hakdogan.com/practice/multi-select');

  await page.getByTestId('ms-cypress').click();
  await page.getByTestId('ms-playwright').click();
  await page.getByTestId('ms-k6').click();

  await expect(page.locator('[data-testid^="ms-chip-"]')).toHaveCount(3);
  await page.getByTestId('ms-remove-cypress').click();
  await expect(page.locator('[data-testid^="ms-chip-"]')).toHaveCount(2);
});

Headed Test Playback

Simulated headed-browser flow — no real browser is launched.

Automation-style playback (Playwright-shaped logs). No real browser; no commands run on your machine.

Mixed: fill() for stable fields, type() / press() where keyboard semantics matter.

Idlemulti-select.spec.ts
Failure demos
https://lab.hakdogan.com/practice/multi-select
playwright · headed · chromium0.00s
# awaiting Run Test · terminal scrolls automatically
Steps
  1. Show multi-select field
  2. Open dropdown
  3. Type "play"
  4. Select Playwright
  5. Select TypeScript
  6. Select Cypress
  7. Remove Cypress chip
  8. Assert chip count is 2
  9. Pass
StatusIdle
BrowserChromium
FrameworkPlaywright + TypeScript
Elapsed (live)--
Specmulti-select.spec.ts