# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: vendors.spec.js >> Vendor Management — Add / Edit / Delete >> Add tab opens empty form
- Location: tests/vendors.spec.js:271:3

# Error details

```
Error: expect(locator).not.toHaveText(expected) failed

Locator: locator('#t-total')
Expected: not "—"
Timeout: 8000ms
Error: element(s) not found

Call log:
  - Expect "not toHaveText" with timeout 8000ms
  - waiting for locator('#t-total')

```

```yaml
- text: 404 page not found
```

# Test source

```ts
  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');
  164 |     await page.goto(BASE_URL);
  165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });
  166 |   });
  167 | 
  168 |   test('vendor rows render', async ({ page }) => {
  169 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 6000 });
  170 |     const count = await page.locator('.v-row').count();
  171 |     expect(count).toBeGreaterThan(0);
  172 |   });
  173 | 
  174 |   test('type chip "Family" filters the list', async ({ page }) => {
  175 |     await page.locator('#type-chips .chip[data-type="family"]').click();
  176 |     const subText = await page.locator('#list-sub').textContent();
  177 |     expect(subText).toContain('Family');
  178 |   });
  179 | 
  180 |   test('type chip "Vendors" filters to vendor type', async ({ page }) => {
  181 |     await page.locator('#type-chips .chip[data-type="vendor"]').click();
  182 |     await expect(page.locator('.v-row .v-row-badge.vendor').first()).toBeVisible({ timeout: 3000 });
  183 |   });
  184 | 
  185 |   test('search by name filters rows', async ({ page }) => {
  186 |     await page.locator('#search-q').fill('mukesh');
  187 |     await page.waitForTimeout(300);  // debounce
  188 |     const subText = await page.locator('#list-sub').textContent();
  189 |     expect(subText).toContain('mukesh');
  190 |     const rows = await page.locator('.v-row').count();
  191 |     expect(rows).toBeGreaterThan(0);
  192 |   });
  193 | 
  194 |   test('search with no match shows empty state', async ({ page }) => {
  195 |     await page.locator('#search-q').fill('zzzzzzzz_nope_' + Date.now());
  196 |     await page.waitForTimeout(300);
  197 |     await expect(page.locator('#v-list .empty')).toBeVisible({ timeout: 3000 });
  198 |   });
  199 | 
  200 |   test('clearing type chip back to All shows all rows', async ({ page }) => {
  201 |     await page.locator('#type-chips .chip[data-type="vendor"]').click();
  202 |     await page.waitForTimeout(200);
  203 |     const filteredCount = await page.locator('.v-row').count();
  204 |     await page.locator('#type-chips .chip[data-type=""]').click();
  205 |     await page.waitForTimeout(200);
  206 |     const allCount = await page.locator('.v-row').count();
  207 |     expect(allCount).toBeGreaterThanOrEqual(filteredCount);
  208 |   });
  209 | });
  210 | 
  211 | // ─── DETAIL SCREEN ──────────────────────────────────────────────────
  212 | test.describe(`${PWA_NAME} — Detail`, () => {
  213 |   test.beforeEach(async ({ page }) => {
  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');
  215 |     await page.goto(BASE_URL);
  216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });
  217 |   });
  218 | 
  219 |   test('tapping a row opens detail screen', async ({ page }) => {
  220 |     await page.locator('.v-row').first().click();
  221 |     await expect(page.locator('#s-detail.active')).toBeVisible({ timeout: 4000 });
  222 |     await expect(page.locator('#d-title')).not.toBeEmpty();
  223 |   });
  224 | 
  225 |   test('Info tab is selected by default', async ({ page }) => {
  226 |     await page.locator('.v-row').first().click();
  227 |     await expect(page.locator('#d-panel-info')).toBeVisible();
  228 |     await expect(page.locator('#d-panel-ledger')).toBeHidden();
  229 |   });
  230 | 
  231 |   test('switching to Ledger tab loads it', async ({ page }) => {
  232 |     await page.locator('.v-row').first().click();
  233 |     await page.locator('#s-detail .chip[data-tab="ledger"]').click();
  234 |     await expect(page.locator('#d-panel-ledger')).toBeVisible();
  235 |     await expect(page.locator('#d-panel-info')).toBeHidden();
  236 |     // Tiles should populate (or show — for zero data)
  237 |     await expect(page.locator('#l-out')).not.toHaveText('—', { timeout: 4000 });
  238 |   });
  239 | 
  240 |   test('Back arrow returns to list', async ({ page }) => {
  241 |     await page.locator('.v-row').first().click();
  242 |     await page.locator('#s-detail .back-btn').click();
  243 |     await expect(page.locator('#s-list.active')).toBeVisible();
  244 |   });
  245 | 
  246 |   test('Edit button opens form pre-filled', async ({ page }) => {
  247 |     await page.locator('.v-row').first().click();
  248 |     const titleText = await page.locator('#d-title').textContent();
  249 |     await page.locator('#s-detail .hdr-btn:has-text("Edit")').click();
  250 |     await expect(page.locator('#s-form.active')).toBeVisible();
  251 |     await expect(page.locator('#f-name')).toHaveValue(titleText.trim());
  252 |   });
  253 | });
  254 | 
  255 | // ─── ADD / EDIT FORM ────────────────────────────────────────────────
  256 | test.describe(`${PWA_NAME} — Add / Edit / Delete`, () => {
  257 |   const TEST_NAME = `PWtest Vendor ${RUN_ID}`;
  258 |   // payee_id will be 'v_pwtest_vendor_' + RUN_ID
  259 | 
  260 |   test.beforeEach(async ({ page }) => {
  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');
  262 |     await page.goto(BASE_URL);
> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });
      |                                                ^ Error: expect(locator).not.toHaveText(expected) failed
  264 |   });
  265 | 
  266 |   test.afterAll(async () => {
  267 |     // Hard-delete the test payee whether the test passes or fails
  268 |     await dbCleanup('v_pwtest_vendor_');
  269 |   });
  270 | 
  271 |   test('Add tab opens empty form', async ({ page }) => {
  272 |     await page.locator('#nav-add').click();
  273 |     await expect(page.locator('#s-form.active')).toBeVisible();
  274 |     await expect(page.locator('#f-name')).toHaveValue('');
  275 |     await expect(page.locator('#f-title')).toHaveText('Add Vendor');
  276 |   });
  277 | 
  278 |   test('saving with empty name shows error toast', async ({ page }) => {
  279 |     await page.locator('#nav-add').click();
  280 |     await page.locator('#f-save-btn').click();
  281 |     await expect(page.locator('#toast.show')).toContainText('required', { timeout: 4000 });
  282 |   });
  283 | 
  284 |   test('full add → detail → edit → soft-delete flow', async ({ page }) => {
  285 |     // Add
  286 |     await page.locator('#nav-add').click();
  287 |     await page.locator('#f-name').fill(TEST_NAME);
  288 |     await page.locator('#f-type').selectOption('vendor');
  289 |     await page.locator('#f-proprietor').fill('PW Proprietor');
  290 |     await page.locator('#f-upi-receipt').fill('PW UPI');
  291 |     await page.locator('#f-phone').fill('9999000011');
  292 |     await page.locator('#f-email').fill('pw@test.in');
  293 |     await page.locator('#f-gst').fill('29ABCDE1234F1Z5');  // 15 chars (valid GSTIN length)
  294 |     await page.locator('#f-pan').fill('ABCDE1234F');         // 10 chars (valid PAN length)
  295 |     await page.locator('#f-contact').fill('PW Contact');
  296 |     await page.locator('#f-address').fill('PW Address');
  297 |     await page.locator('#f-aliases').fill('PW Alias 1, PW Alias 2');
  298 |     await page.locator('#f-save-btn').click();
  299 |     await expect(page.locator('#toast.ok')).toContainText('added', { timeout: 8000 });
  300 |     // Returns to list
  301 |     await expect(page.locator('#s-list.active')).toBeVisible();
  302 | 
  303 |     // Verify in list via search
  304 |     await page.locator('#search-q').fill(TEST_NAME);
  305 |     await page.waitForTimeout(300);
  306 |     await expect(page.locator('.v-row', { hasText: TEST_NAME })).toBeVisible({ timeout: 4000 });
  307 | 
  308 |     // Open detail
  309 |     await page.locator('.v-row', { hasText: TEST_NAME }).click();
  310 |     await expect(page.locator('#d-title')).toHaveText(TEST_NAME);
  311 |     // Verify proprietor + GST visible in info
  312 |     await expect(page.locator('#d-info-card')).toContainText('PW Proprietor');
  313 |     await expect(page.locator('#d-info-card')).toContainText('29ABCDE1234F1Z5');
  314 | 
  315 |     // Edit
  316 |     await page.locator('#s-detail .hdr-btn:has-text("Edit")').click();
  317 |     await expect(page.locator('#f-title')).toHaveText('Edit Vendor');
  318 |     await page.locator('#f-address').fill('PW Address — UPDATED');
  319 |     await page.locator('#f-save-btn').click();
  320 |     await expect(page.locator('#toast.ok')).toContainText('Saved', { timeout: 8000 });
  321 |     // Returns to detail; verify update
  322 |     await expect(page.locator('#d-info-card')).toContainText('UPDATED', { timeout: 4000 });
  323 | 
  324 |     // Soft-delete from edit form
  325 |     await page.locator('#s-detail .hdr-btn:has-text("Edit")').click();
  326 |     await page.locator('#f-delete-row button').click();
  327 |     // Accept the confirm dialog
  328 |     page.on('dialog', d => d.accept());
  329 |     await page.locator('#f-delete-row button').click();
  330 |     await expect(page.locator('#toast.ok')).toContainText('deactivated', { timeout: 8000 });
  331 |     // Should be back on list
  332 |     await expect(page.locator('#s-list.active')).toBeVisible();
  333 |     // Vendor should no longer appear in active list
  334 |     await page.locator('#search-q').fill(TEST_NAME);
  335 |     await page.waitForTimeout(300);
  336 |     await expect(page.locator('.v-row', { hasText: TEST_NAME })).toHaveCount(0);
  337 |   });
  338 | });
  339 | 
  340 | // ─── SEARCH ACROSS FIELDS ───────────────────────────────────────────
  341 | test.describe(`${PWA_NAME} — Search across all fields (client-side)`, () => {
  342 |   test.beforeEach(async ({ page }) => {
  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');
  344 |     await page.goto(BASE_URL);
  345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });
  346 |   });
  347 | 
  348 |   test('search by partial phone matches', async ({ page }) => {
  349 |     // Use an existing payee's phone if available; just confirm the input drives filter logic
  350 |     await page.locator('#search-q').fill('98');
  351 |     await page.waitForTimeout(300);
  352 |     const subText = await page.locator('#list-sub').textContent();
  353 |     expect(subText).toMatch(/of \d+/);
  354 |   });
  355 | 
  356 |   test('search by alias (existing payee with aliases)', async ({ page }) => {
  357 |     // Most payees may have no aliases; just confirm the input accepts and updates count
  358 |     await page.locator('#search-q').fill('xyz_nope_alias_q');
  359 |     await page.waitForTimeout(300);
  360 |     await expect(page.locator('#v-list .empty')).toBeVisible();
  361 |   });
  362 | });
  363 | 
```