# 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: upi_pay.spec.js >> Form Validation >> clear form resets all fields
- Location: tests/upi_pay.spec.js:290:3

# Error details

```
Test timeout of 30000ms exceeded while running "beforeEach" hook.
```

```
Error: locator.click: Test timeout of 30000ms exceeded.
Call log:
  - waiting for locator('.btn-cta')

```

# Page snapshot

```yaml
- generic [ref=e2]: 404 page not found
```

# Test source

```ts
  161 |   test('NEFT mode: UPI section hidden, ref label is UTR No.', async ({ page }) => {
  162 |     await page.locator('#mode-chips [data-mode="neft"]').click();
  163 |     await expect(page.locator('#upi-section')).not.toBeVisible();
  164 |     await expect(page.locator('#ref-label')).toHaveText('UTR No.');
  165 |   });
  166 | 
  167 |   test('RTGS mode: UPI section hidden, ref label is UTR No.', async ({ page }) => {
  168 |     await page.locator('#mode-chips [data-mode="rtgs"]').click();
  169 |     await expect(page.locator('#upi-section')).not.toBeVisible();
  170 |     await expect(page.locator('#ref-label')).toHaveText('UTR No.');
  171 |   });
  172 | 
  173 |   test('IMPS mode: UPI section hidden', async ({ page }) => {
  174 |     await page.locator('#mode-chips [data-mode="imps"]').click();
  175 |     await expect(page.locator('#upi-section')).not.toBeVisible();
  176 |   });
  177 | 
  178 |   test('UPI mode: ref label is UPI Ref No.', async ({ page }) => {
  179 |     await expect(page.locator('#ref-label')).toHaveText('UPI Ref No.');
  180 |   });
  181 | 
  182 |   test('switching back to UPI shows UPI section', async ({ page }) => {
  183 |     await page.locator('#mode-chips [data-mode="neft"]').click();
  184 |     await page.locator('#mode-chips [data-mode="upi"]').click();
  185 |     await expect(page.locator('#upi-section')).toBeVisible();
  186 |   });
  187 | });
  188 | 
  189 | // ─────────────────────────────────────────────────────────────────
  190 | // UPI APP PICKER
  191 | // ─────────────────────────────────────────────────────────────────
  192 | test.describe('UPI App Picker', () => {
  193 |   test.beforeEach(async ({ page }) => {
  194 |     await injectSession(page, SESSION.pramod);
  195 |     await page.goto(BASE_URL);
  196 |     await page.locator('.btn-cta').click();
  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });
  198 |   });
  199 | 
  200 |   test('4 UPI app buttons rendered', async ({ page }) => {
  201 |     const count = await page.locator('#upi-app-row .upi-app-btn').count();
  202 |     expect(count).toBe(4);
  203 |   });
  204 | 
  205 |   test('tapping an app button selects it', async ({ page }) => {
  206 |     await page.locator('#upi-app-row .upi-app-btn', { hasText: 'GPay' }).click();
  207 |     await expect(page.locator('#upi-app-row .upi-app-btn', { hasText: 'GPay' })).toHaveClass(/sel/);
  208 |     await expect(page.locator('#launch-app-lbl')).toHaveText('GPay');
  209 |   });
  210 | });
  211 | 
  212 | // ─────────────────────────────────────────────────────────────────
  213 | // PAYEE SEARCH
  214 | // ─────────────────────────────────────────────────────────────────
  215 | test.describe('Payee Search', () => {
  216 |   test.beforeEach(async ({ page }) => {
  217 |     await injectSession(page, SESSION.pramod);
  218 |     await page.goto(BASE_URL);
  219 |     await page.locator('.btn-cta').click();
  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });
  221 |   });
  222 | 
  223 |   test('typing 2+ chars triggers search and shows dropdown', async ({ page }) => {
  224 |     await page.locator('#payee-q').fill('ash');
  225 |     await expect(page.locator('#payee-drop')).toBeVisible({ timeout: 4000 });
  226 |     const count = await page.locator('#payee-drop .pay-res').count();
  227 |     expect(count).toBeGreaterThan(0);
  228 |   });
  229 | 
  230 |   test('selecting a result shows payee card', async ({ page }) => {
  231 |     await page.locator('#payee-q').fill('ash');
  232 |     await expect(page.locator('#payee-drop .pay-res').first()).toBeVisible({ timeout: 4000 });
  233 |     await page.locator('#payee-drop .pay-res').first().click();
  234 |     await expect(page.locator('#payee-card .payee-sel')).toBeVisible();
  235 |     await expect(page.locator('#payee-drop')).not.toBeVisible();
  236 |   });
  237 | 
  238 |   test('clear button removes selected payee', async ({ page }) => {
  239 |     await page.locator('#payee-q').fill('ash');
  240 |     await expect(page.locator('#payee-drop .pay-res').first()).toBeVisible({ timeout: 4000 });
  241 |     await page.locator('#payee-drop .pay-res').first().click();
  242 |     await page.locator('.payee-clr').click();
  243 |     await expect(page.locator('#payee-card')).not.toBeVisible();
  244 |     await expect(page.locator('#payee-q')).toHaveValue('');
  245 |   });
  246 | 
  247 |   test('searching for harish returns Harish Kumar Lal', async ({ page }) => {
  248 |     await page.locator('#payee-q').fill('harish');
  249 |     await expect(page.locator('#payee-drop')).toBeVisible({ timeout: 4000 });
  250 |     await expect(page.locator('#payee-drop .pay-res-name', { hasText: 'Harish Kumar Lal' })).toBeVisible();
  251 |   });
  252 | });
  253 | 
  254 | // ─────────────────────────────────────────────────────────────────
  255 | // FORM VALIDATION
  256 | // ─────────────────────────────────────────────────────────────────
  257 | test.describe('Form Validation', () => {
  258 |   test.beforeEach(async ({ page }) => {
  259 |     await injectSession(page, SESSION.pramod);
  260 |     await page.goto(BASE_URL);
> 261 |     await page.locator('.btn-cta').click();
      |                                    ^ Error: locator.click: Test timeout of 30000ms exceeded.
  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });
  263 |   });
  264 | 
  265 |   test('submit with no payee shows error toast', async ({ page }) => {
  266 |     await page.locator('#pay-amount').fill('1000');
  267 |     await page.locator('#reason-chips .chip').first().click();
  268 |     await page.locator('#pay-submit').click();
  269 |     await expect(page.locator('#toast')).toContainText('Select a payee', { timeout: 3000 });
  270 |   });
  271 | 
  272 |   test('submit with no amount shows error toast', async ({ page }) => {
  273 |     await page.locator('#payee-q').fill('ash');
  274 |     await expect(page.locator('#payee-drop .pay-res').first()).toBeVisible({ timeout: 4000 });
  275 |     await page.locator('#payee-drop .pay-res').first().click();
  276 |     await page.locator('#reason-chips .chip').first().click();
  277 |     await page.locator('#pay-submit').click();
  278 |     await expect(page.locator('#toast')).toContainText('valid amount', { timeout: 3000 });
  279 |   });
  280 | 
  281 |   test('submit with no reason shows error toast', async ({ page }) => {
  282 |     await page.locator('#payee-q').fill('ash');
  283 |     await expect(page.locator('#payee-drop .pay-res').first()).toBeVisible({ timeout: 4000 });
  284 |     await page.locator('#payee-drop .pay-res').first().click();
  285 |     await page.locator('#pay-amount').fill('1000');
  286 |     await page.locator('#pay-submit').click();
  287 |     await expect(page.locator('#toast')).toContainText('reason', { timeout: 3000 });
  288 |   });
  289 | 
  290 |   test('clear form resets all fields', async ({ page }) => {
  291 |     await page.locator('#pay-amount').fill('5000');
  292 |     await page.locator('#pay-notes').fill('test');
  293 |     await page.locator('#reason-chips .chip').first().click();
  294 |     await page.locator('.btn-clr').click();
  295 |     await expect(page.locator('#pay-amount')).toHaveValue('');
  296 |     await expect(page.locator('#pay-notes')).toHaveValue('');
  297 |     // Reason chips all unselected
  298 |     const selChips = await page.locator('#reason-chips .chip.sel').count();
  299 |     expect(selChips).toBe(0);
  300 |   });
  301 | });
  302 | 
  303 | // ─────────────────────────────────────────────────────────────────
  304 | // URL PARAM PRE-FILL (custodian transfer)
  305 | // ─────────────────────────────────────────────────────────────────
  306 | test.describe('URL Param Pre-fill', () => {
  307 |   test('context=custodian-transfer pre-selects reason and opens pay screen', async ({ page }) => {
  308 |     await injectSession(page, SESSION.pramod);
  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');
  310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });
  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);
  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');
  313 |     await expect(page.locator('#reason-notice')).toBeVisible();
  314 |   });
  315 | 
  316 |   test('today date is always pre-filled', async ({ page }) => {
  317 |     await injectSession(page, SESSION.pramod);
  318 |     await page.goto(BASE_URL);
  319 |     await page.locator('.btn-cta').click();
  320 |     const today = new Date().toISOString().slice(0, 10);
  321 |     await expect(page.locator('#pay-date')).toHaveValue(today);
  322 |   });
  323 | 
  324 |   test('to=shambhu param auto-selects Shambhu as payee', async ({ page }) => {
  325 |     await injectSession(page, SESSION.pramod);
  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');
  327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });
  328 |     // Payee card should appear with Shambhu's name
  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });
  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');
  331 |   });
  332 | });
  333 | 
  334 | // ─────────────────────────────────────────────────────────────────
  335 | // PHASE 4 — INTEGRATION (Custodian ↔ UPI Pay round-trip)
  336 | // ─────────────────────────────────────────────────────────────────
  337 | test.describe('Phase 4 — Integration', () => {
  338 | 
  339 |   test('advance payment: submit success shows toast and stays in UPI Pay', async ({ page }) => {
  340 |     // Mock the DB advance write so no real record is created
  341 |     await page.route('**/db/advances', route => route.fulfill({ status: 201, body: '' }));
  342 |     // Also mock wallet + recent endpoints for dashboard reload
  343 |     await page.route('**/db/wallets**', route => route.fulfill({
  344 |       status: 200, headers: { 'Content-Type': 'application/json' },
  345 |       body: JSON.stringify([{ custodian_id: 'pramod', balance: 0 }])
  346 |     }));
  347 |     await page.route('**/db/advance_detail**', route => route.fulfill({
  348 |       status: 200, headers: { 'Content-Type': 'application/json' }, body: '[]'
  349 |     }));
  350 |     await page.route('**/db/transfer_detail**', route => route.fulfill({
  351 |       status: 200, headers: { 'Content-Type': 'application/json' }, body: '[]'
  352 |     }));
  353 | 
  354 |     await injectSession(page, SESSION.pramod);
  355 |     await page.goto(BASE_URL);
  356 |     await page.locator('.btn-cta').click();
  357 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });
  358 | 
  359 |     // Select payee
  360 |     await page.locator('#payee-q').fill('ash');
  361 |     await expect(page.locator('#payee-drop .pay-res').first()).toBeVisible({ timeout: 4000 });
```