# Locator Naming Convention & Action DSL

**Scope:** SE (Automation) — used when mapping PE's Human Steps → Playwright/Selenium locators (Phase 3 of FuncTest workflow)

**Philosophy:** Locators use **business meaning**, not UI text. State is separate from element identity.

---

## Lifecycle & Ownership

The testid for an element flows through the SDLC with one **single source of truth**: the `### Locator Map` table in each UI stack section of the Feature-Technical file.

| Stage | Command | Action on the Locator Map |
|---|---|---|
| **Plan** | `/tas-plan` | Seeds the `### Locator Map` (best-effort) from AC + Logic Flow. `Origin: plan`. |
| **Build** | `/tas-dev` | Injects each testid into source (`data-testid` web / `testID` app). Any built element missing from the Map is **back-filled** with `Origin: dev`. Mobile also syncs to `apps/mobile/e2e/test-ids.ts` (derived registry). |
| **Test** | `/tas-functest-web`, `/tas-functest-mobile` | **Consume** the Map for `getByTestId` / `by.id`, and **write-back** the Action DSL into the FuncTest §4 "Execution Locators" column. Never invent a testid — a missing one signals a `/tas-dev` gap. |
| **Gate** | `feature-done.md` | Locator parity check: every Map testid exists on a rendered component; Execution Locators match the Map. |

**Scope — functional / E2E only.** Locators (`data-testid`/`testID`) are for black-box DOM/UI selection in Playwright/Detox. **Unit tests do NOT use locators** — query by role/label/text per Testing Library; coupling unit tests to testids is an anti-pattern.

---

## Core Naming Pattern

```
{prefix}_{domain}_{functional_name}_{index?}
```

| Component | Definition | Example |
|---|---|---|
| `prefix` | Element type (see Prefix Table below) | `ipt`, `btn`, `lbl` |
| `domain` | Business module/area | `auth`, `order`, `product`, `payment` |
| `functional_name` | Business meaning (not HTML class or UI text) | `email`, `submit`, `status` |
| `index` | Optional list position | `idx_1`, `idx_2`, or `dyn` (dynamic) |

**Examples:**
- `ipt_auth_email` — Input field for authentication email
- `btn_order_confirm` — Button to confirm order
- `lbl_payment_status` — Label showing payment status
- `crd_product_item_idx_1` — First product card in a list

---

## Prefix System

| Type | Prefix | Use Case | Example |
|---|---|---|---|
| **Input** | `ipt` | Text fields, password, textarea | `ipt_login_email` |
| **Button** | `btn` | Clickable buttons | `btn_login_submit` |
| **Label/Text** | `lbl` | Static text, headings, values | `lbl_order_total` |
| **Checkbox** | `chk` | Checkbox inputs | `chk_terms_agree` |
| **Radio** | `rdo` | Radio button inputs | `rdo_gender_male` |
| **Dropdown** | `sel` | Select/combo box | `sel_country_list` |
| **Navigation** | `nav` | Tabs, breadcrumbs, menu items | `nav_main_profile` |
| **Loading** | `ldn` | Spinner, progress indicator | `ldn_global_app` |
| **Toggle** | `tgl` | Switch/toggle controls | `tgl_2fa_enabled` |
| **Container** | `ctn` | Group/wrapper div (scope for children) | `ctn_auth_form` |
| **Card** | `crd` | Card component (clickable container) | `crd_product_item` |
| **Item** | `itm` | List item (within a list) | `itm_comment_idx_1` |
| **Skeleton** | `skn` | Loading skeleton/placeholder | `skn_product_list` |
| **Toast** | `tst` | Toast/notification message | `tst_order_success` |
| **Image** | `img` | Image or icon | `img_avatar_user` |
| **Overlay** | `ovl` | Modal, dialog, drawer, popover, tooltip | `ovl_mdl_login` |

---

## Overlay Subtypes

When using `ovl` prefix, include subtype:

| Subtype | Code | Use Case | Example |
|---|---|---|---|
| **Modal** | `mdl` | Full-screen overlay | `ovl_mdl_auth_confirm` |
| **Dialog** | `dlg` | Alert/confirmation dialog | `ovl_dlg_delete_warning` |
| **Drawer** | `drw` | Side drawer/sidebar panel | `ovl_drw_settings` |
| **Popover** | `pop` | Floating popover panel | `ovl_pop_share_menu` |
| **Tooltip** | `tip` | Tooltip on hover | `ovl_tip_cvv_help` |
| **Bottom Sheet** | `bts` | Mobile bottom sheet | `ovl_bts_filter_options` |
| **Sidebar** | `sid` | Fixed sidebar navigation | `ovl_sid_main_nav` |

**Format:** `ovl_{subtype}_{domain}_{functional_name}_{index?}`

**Examples:**
- `ovl_mdl_login_form` — Login modal
- `ovl_dlg_delete_confirm` — Delete confirmation dialog
- `ovl_drw_settings_panel` — Settings drawer
- `ovl_tip_password_hint` — Password help tooltip

---

## State Values

States are used in **assertions**, NOT in the testid itself.

```
assert:{testid} == {state}
```

| State | Use Case | Example |
|---|---|---|
| `visible` | Element is on screen | `assert:btn_submit == visible` |
| `hidden` | Element is not on screen | `assert:div_error == hidden` |
| `enable` | Button/input can be interacted | `assert:btn_submit == enable` |
| `disable` | Button/input cannot be interacted | `assert:btn_submit == disable` |
| `checked` | Checkbox/radio is selected | `assert:chk_agree == checked` |
| `unchecked` | Checkbox/radio is not selected | `assert:chk_agree == unchecked` |
| `selected` | Option/tab is active | `assert:nav_profile == selected` |
| `loading` | Element is in loading state | `assert:ldn_app == loading` |
| `error` | Input/element has error state | `assert:ipt_email == error` |
| `empty` | Field/list is empty | `assert:ctn_results == empty` |
| `on` | Toggle is turned on | `assert:tgl_2fa == on` |
| `off` | Toggle is turned off | `assert:tgl_2fa == off` |
| `ready` | Element is ready to interact | `assert:ctn_form == ready` |
| `success` | Operation succeeded | `assert:tst_save == success` |
| `warning` | Warning state (toast, banner) | `assert:tst_alert == warning` |
| `active` | Element is active/focused | `assert:nav_tab == active` |

---

## Action DSL (Lean)

| Action | Syntax | Description | Example |
|---|---|---|---|
| **click** | `click:{testid}` | Click element | `click:btn_login_submit` |
| **type** | `type:{testid} "{value}"` | Type text into input | `type:ipt_email "user@example.com"` |
| **clear** | `clear:{testid}` | Clear input field | `clear:ipt_search` |
| **select** | `select:{testid} "{value}"` | Select dropdown option | `select:sel_country "Vietnam"` |
| **check** | `check:{testid}` | Check checkbox | `check:chk_agree` |
| **uncheck** | `uncheck:{testid}` | Uncheck checkbox | `uncheck:chk_subscribe` |
| **toggle** | `toggle:{testid}` | Toggle switch on/off | `toggle:tgl_2fa` |
| **scroll** | `scroll:{testid}` | Scroll to element | `scroll:itm_product_idx_5` |
| **wait_visible** | `wait_visible:{testid}` | Wait for element to appear | `wait_visible:ldn_app` |
| **wait_invisible** | `wait_invisible:{testid}` | Wait for element to disappear | `wait_invisible:skn_list` |
| **assert** | `assert:{testid} == {state}` or `assert:{testid} == "{text}"` | Verify element state or text | `assert:btn_submit == enable` |

---

## ACTION × ELEMENT Matrix

### Input (`ipt_`)

```
ipt_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `type`, `clear` | `type:ipt_login_email "test@example.com"` | Type data. Assert state for validation errors. |
| `assert` state | `assert:ipt_email == error` | Check if input is disabled or shows error. |
| `assert` text | `assert:ipt_search == empty` | Verify input is cleared. |

---

### Button (`btn_`)

```
btn_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `click` | `assert:btn_submit == enable` → `click:btn_submit` | Always assert state == enable before clicking. |
| `assert` state | `assert:btn_submit == disable` | Verify button is disabled when expected (e.g., form invalid). |
| `wait_visible` | `wait_visible:btn_next` → `click:btn_next` | Button may be hidden until condition met. |

---

### Label / Text (`lbl_`)

```
lbl_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `assert` text | `assert:lbl_order_status == "Order Confirmed"` | Verify displayed text (i18n, dynamic values). |
| `assert` state | `assert:lbl_error_message == visible` | Check if label is shown/hidden. |

---

### Checkbox (`chk_`)

```
chk_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `check`, `uncheck` | `check:chk_remember` → `assert:chk_remember == checked` | Check/uncheck, then verify state. |
| `assert` state | `assert:chk_agree == disable` | Verify if checkbox is locked. |

---

### Radio (`rdo_`)

```
rdo_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `select` | `select:rdo_gender_male` → `assert:rdo_gender_male == selected` | Select option, then verify. |
| `assert` state | `assert:rdo_payment_method == disable` | Verify if radio is disabled. |

---

### Dropdown (`sel_`)

```
sel_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `click` | `click:sel_country` → `select:sel_country "Vietnam"` | Click to open, then select value. |
| `select` | `select:sel_country "Vietnam"` | Select option directly. |
| `assert` text | `assert:sel_country == "Vietnam"` | Verify selected value. |
| `assert` state | `assert:sel_country == disable` | Verify if dropdown is disabled. |

---

### Navigation / Tab (`nav_`)

```
nav_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `click` | `click:nav_profile` → `assert:nav_profile == active` | Click tab, then verify it's active. |
| `assert` state | `assert:nav_settings == disable` | Verify if tab is locked. |

---

### Loading / Spinner (`ldn_`)

```
ldn_{domain}_{functional_name}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `wait_visible` | `wait_visible:ldn_app` | Wait for spinner to appear. |
| `wait_invisible` | `wait_invisible:ldn_app` | Block next actions until loading completes. |
| `assert` state | `assert:ldn_app == loading` | Verify spinner is visible. |

**⚠️ IMPORTANT:** Never interact with elements while skeleton/loader is visible. Always `wait_invisible` first.

---

### Toggle / Switch (`tgl_`)

```
tgl_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `toggle` | `assert:tgl_2fa == off` → `toggle:tgl_2fa` → `assert:tgl_2fa == on` | Toggle, then verify new state. |
| `assert` state | `assert:tgl_darkmode == disable` | Verify if toggle is locked. |

---

### Container / Group (`ctn_`)

```
ctn_{domain}_{functional_name}_{index?}
```

> Use to scope child elements. Useful for complex nested layouts.

| Allowed Actions | Example | Strategy |
|---|---|---|
| `assert` state | `assert:ctn_form == ready` → `type:ctn_form >> ipt_email "..."` | Assert container is ready before interacting with children. |
| Scope children | `click:ctn_modal >> btn_confirm` | Use `>>` to scope child elements. |

**Child Access Syntax:** `ctn_parent >> ipt_child`

---

### Card (`crd_`)

```
crd_{domain}_{functional_name}_{index?}
```

| Allowed Actions | Example | Strategy |
|---|---|---|
| `click` | `assert:crd_product == ready` → `click:crd_product` | Assert ready state before clicking. |
| `assert` state | `assert:crd_product == loading` | Verify if card is loading. |
| `scroll` | `scroll:crd_product` | Scroll to card before interacting. |

---

### Item / List Item (`itm_`)

```
itm_{domain}_{functional_name}_{index?}
```

**MUST use index** when in a dynamic list.

| Allowed Actions | Example | Strategy |
|---|---|---|
| `click` | `scroll:itm_comment_idx_2` → `click:itm_comment_idx_2` | Scroll to list item, then click. |
| `assert` state | `assert:itm_row_idx_1 == selected` | Verify if item is selected. |

**Index Format:**
- `itm_comment_idx_1` — First item (1-indexed, not 0)
- `itm_comment_idx_n` — Nth item
- Use `idx_` prefix for list-based indexing

---

### Skeleton (`skn_`)

```
skn_{domain}_{functional_name}
```

> Placeholder shown while content loads. NEVER interact while skeleton visible.

| Allowed Actions | Example | Strategy |
|---|---|---|
| `wait_invisible` | `wait_invisible:skn_product_list` → `assert:crd_product == ready` | Wait for skeleton to disappear, then interact. |
| `assert` state | `assert:skn_list == hidden` | Verify skeleton is gone. |

---

### Toast / Notification (`tst_`)

```
tst_{domain}_{functional_name}
```

> Auto-dismisses. Use `wait_visible` before asserting.

| Allowed Actions | Example | Strategy |
|---|---|---|
| `wait_visible` | `wait_visible:tst_order_saved` → `assert:tst_order_saved == success` | Wait for toast to appear. |
| `assert` state | `assert:tst_error == visible` | Check if error toast is shown. |
| `assert` text | `assert:tst_save == "Changes saved"` | Verify toast message. |

---

### Image / Icon (`img_`)

```
img_{domain}_{functional_name}_{index?}
```

> Do not interact directly. Check state only.

| Allowed Actions | Example | Strategy |
|---|---|---|
| `assert` state | `assert:img_avatar == ready` | Verify image loaded successfully. |
| `assert` state | `assert:img_broken == hidden` | Verify error state is not shown. |

**If image is in a list:** Scope via container or item.
```
ctn_profile >> img_avatar
itm_user_idx_1 >> img_avatar
```

---

### Overlay (`ovl_`)

```
ovl_{subtype}_{domain}_{functional_name}_{index?}
```

**Subtypes:** `mdl` (modal), `dlg` (dialog), `drw` (drawer), `pop` (popover), `tip` (tooltip), `bts` (bottom-sheet), `sid` (sidebar)

| Allowed Actions | Example | Strategy |
|---|---|---|
| `wait_visible` | `wait_visible:ovl_mdl_login` | Wait for overlay to open. |
| `wait_invisible` | `wait_invisible:ovl_mdl_login` | Wait for overlay to close. |
| `assert` state | `assert:ovl_mdl_login == loading` | Check if overlay content is loading. |
| Scope children | `type:ovl_mdl_login >> ipt_email "..."` | Use `>>` to access elements inside overlay. |

**Child Access (within overlay):**
```
type:ovl_mdl_login >> ipt_email "admin@example.com"
click:ovl_mdl_login >> btn_submit
wait_invisible:ovl_mdl_login
```

**Tooltip (`ovl_tip_`):**
```
wait_visible:ovl_tip_password_hint
assert:ovl_tip_password_hint == "Min 8 chars"
wait_invisible:ovl_tip_password_hint
```

---

## Complete Example: Login Flow

### Human Steps (PE writes)
```
1. Navigate to /login
2. Type "admin@example.com" → Email field
3. Type "Pass@123" → Password field
4. Check: Remember me checkbox
5. Click: Login button
— Verify: Redirected to /dashboard
— Teardown: Logout user
```

### Locator DSL (SE writes)
```
navigate_to: /login

type:ipt_auth_email "admin@example.com"
type:ipt_auth_password "Pass@123"
check:chk_auth_remember
assert:btn_auth_submit == enable
click:btn_auth_submit

wait_invisible:ldn_auth_processing
navigate_to: /dashboard
assert:lbl_dashboard_title == visible

teardown:
click:nav_main_logout
wait_visible:ovl_dlg_logout_confirm
click:ovl_dlg_logout_confirm >> btn_confirm
wait_invisible:ovl_dlg_logout_confirm
```

---

## Implementation Guide for SE

### Step 1: Map Human Steps to Locators
- Read PE's Human Steps (from Func-Test.md Section 4)
- Identify element type (button, input, etc.) → determine prefix
- Identify business domain (auth, order, product, etc.)
- Identify functional name (business meaning, not UI text)
- Generate testid: `{prefix}_{domain}_{functional_name}`

### Step 2: Add Actions & Assertions
- For each action (click, type, select), use DSL syntax
- Before critical actions, assert state == enable/ready
- After state-changing actions, assert new state
- For dynamic lists, use `idx_{n}` suffix

### Step 3: Handle Overlays
- Use `ovl_{subtype}_{domain}_{functional_name}` format
- Access child elements via `ovl_xxx >> child_testid`
- Always `wait_visible` to open, `wait_invisible` to confirm closed

### Step 4: Validate Against Matrix
- Check Allowed Actions for each element type
- Do not perform disallowed actions (e.g., don't click a label)
- Follow strategy column for best practices

---

## Quick Reference

**State Assertion:**
```
assert:{testid} == {state_value}
```

**Element in List (with index):**
```
{prefix}_{domain}_{functional_name}_idx_{n}
```

**Child Element (within container/overlay):**
```
{parent_testid} >> {child_testid}
```

**Wait Before Interact:**
```
wait_visible:{testid} → [interact]
wait_invisible:{testid} → [proceed]
```

---

## Common Mistakes

| ❌ WRONG | ✅ CORRECT | Why |
|---|---|---|
| `btn_login_click_me` | `btn_auth_submit` | Functional name, not UI text |
| `input_field_email` | `ipt_auth_email` | Use business domain (auth) |
| `type:lbl_title "text"` | `assert:lbl_title == "text"` | Labels are read-only |
| `click:crd_product_idx_0` | `click:crd_product_idx_1` | Indexing starts at 1, not 0 |
| `assert:btn_submit == "Submit"` | `assert:btn_submit == enable` | State, not text |
| `click:ovl_modal` | `click:ovl_modal >> btn_confirm` | Access children with `>>` |
| `wait_visible:skn_list` → `click:btn_x` | `wait_invisible:skn_list` → `click:crd_item` | Never interact during skeleton loading |

---

## Integration with QC/PE

**PE writes Human Steps (Phase 2):**
```
1. Navigate to /login
2. Type "{value}" → Email field
```

**SE translates to Locator DSL (Phase 3):**
```
navigate_to: /login
type:ipt_auth_email "{value}"
```

**Result:** Deterministic, maintainable, business-aligned test automation.
