TypeScript · @playwright/test
Stock trading with Playwright
A watchlist with prices that tick every second, market and limit orders with buying-power, holdings and price-band checks, a confirm step, and a portfolio with live P&L. Simulated — no real money.
App under test
📊 TradeX
- URL
- /apps/trading
- Login
- TX1001 / Trade@123
- Trading PIN
- 2468
- Starting cash
- ₹5,00,000
- Brokerage
- ₹20 / order
- Limit band
- ±10% of LTP
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Login + trading PIN
BeginnerClient ID + password (tx-login-id, tx-password, tx-login), then a 4-digit PIN (tx-code, tx-verify). Wrong details show attempts left in tx-login-error; 3 failures lock the form (tx-unlock).
- 2
Asserting live data
Intermediateprice-* changes every second — assert format (₹ + 2 decimals) or that it changed, never an exact value.
- 3
Search & watchlist
Beginnersymbol-search → add-<SYM> adds a watch-row; remove-<SYM> deletes it.
- 4
Quantity validation
Beginner0, decimals or text in order-qty show error-order-qty.
- 5
Buying power
IntermediateOrder more than cash allows; the error tells you the affordable quantity.
- 6
Limit band
IntermediateA limit price more than 10% away from LTP is rejected in error-limit-price.
- 7
Buy then sell
AdvancedBuy at market, then sell part of the holding; holding-qty-<SYM> and cash reconcile (with ₹20 brokerage).
- 8
Open limit orders
AdvancedPlace a limit order near LTP; it may fill on its own — poll order-status or cancel-<id> it.
- 9
Sell more than held
Beginnerside-sell with a quantity above your holding shows “You only hold N share(s)”.
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.
import { test, expect } from '@playwright/test';
// playwright.config.ts → use: { baseURL: 'https://www.practiceqaautomation.com' }
// Locators in this suite: getByRole ×5 · getByLabel ×3 · CSS ×2 · XPath ×4 · getByTestId ×12
test('buys and partly sells a stock', async ({ page }) => {
await page.goto('/apps/trading');
// Sign in first
await page.getByLabel('Client ID').fill('TX1001');
await page.locator('#tx-password').fill('Trade@123');
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByTestId('tx-code').fill('2468');
await page.locator('xpath=//button[normalize-space()="Verify & continue"]').click();
await expect(page.getByTestId('price-INFY')).toBeVisible();
await page.locator('#symbol-search').fill('tata m');
await page.getByTestId('add-TATAMOTORS').click();
await expect(page.getByTestId('price-TATAMOTORS')).toBeVisible();
await page.getByTestId('trade-INFY').click();
await expect(page).toHaveURL(/\/apps\/trading\/trade\/INFY/);
await page.getByRole('button', { name: 'Buy', exact: true }).click();
await page.locator('xpath=//button[normalize-space()="MarketFill now at LTP"]').click();
await page.getByLabel('Quantity').fill('10');
await page.getByTestId('review-order').click();
await expect(page.getByTestId('review-qty')).toContainText('10');
await page.getByRole('button', { name: 'Confirm' }).click();
await expect(page.getByTestId('order-result')).toContainText('Filled');
await page.locator('xpath=//a[normalize-space()="Portfolio"]').click();
await expect(page.getByTestId('holding-qty-INFY')).toContainText('10');
// Sell part of the position
await page.getByRole('link', { name: 'Sell' }).click();
await page.getByLabel('Quantity').fill('4');
await page.getByTestId('review-order').click();
await page.getByRole('button', { name: 'Confirm' }).click();
await expect(page.getByTestId('order-result')).toContainText('Sold 4 INFY');
await page.locator('xpath=//a[normalize-space()="Portfolio"]').click();
await expect(page.getByTestId('holding-qty-INFY')).toContainText('6');
});