JavaScript and TypeScript for QA
JavaScript dominates frontend testing, and TypeScript adds the type safety that large test suites need. If the application under test is built with React, Vue, Angular, or Node.js, your test framework will almost certainly be JavaScript or TypeScript based.
Why JavaScript/TypeScript for QA
| Aspect | JavaScript | TypeScript |
|---|---|---|
| Test frameworks | Jest, Mocha, Playwright Test, Cypress | Same, with type checking |
| Browser automation | Selenium, Playwright, Cypress, Puppeteer | Same, with autocompletion |
| API testing | axios, fetch, supertest | Same, with typed responses |
| Where it dominates | Frontend testing, E2E testing, full-stack apps | Same, with better maintainability |
| Learning curve | Lower | Slightly higher (types) |
TypeScript Advantage for QA
TypeScript catches errors at compile time that would otherwise become flaky tests at runtime:
// Without TypeScript: this bug is found at runtime (maybe)
const userName = response.data.user.nme; // typo: should be "name"
// With TypeScript: this bug is found immediately in your editor
interface User {
id: number;
name: string;
email: string;
}
const user: User = response.data;
const userName = user.nme; // TypeScript error: Property 'nme' does not exist
Jest: Unit and Integration Testing
Jest is the most popular JavaScript test framework. It is commonly used for unit tests, API tests, and component tests.
// user.test.ts
describe("User API", () => {
let authToken: string;
beforeAll(async () => {
const response = await fetch(`${BASE_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "test@example.com", password: "pass123" })
});
const data = await response.json();
authToken = data.access_token;
});
test("GET /users/me returns current user", async () => {
const response = await fetch(`${BASE_URL}/users/me`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
const user = await response.json();
expect(user).toHaveProperty("id");
expect(user).toHaveProperty("email", "test@example.com");
expect(user).not.toHaveProperty("password");
});
test("GET /users/me without auth returns 401", async () => {
const response = await fetch(`${BASE_URL}/users/me`);
expect(response.status).toBe(401);
});
});
Jest Matchers for QA
// Equality
expect(status).toBe(200); // strict equality
expect(user).toEqual({ id: 1, name: "Alice" }); // deep equality
// Truthiness
expect(token).toBeDefined();
expect(error).toBeNull();
expect(items.length).toBeTruthy();
// Numbers
expect(responseTime).toBeLessThan(1000);
expect(items.length).toBeGreaterThanOrEqual(1);
// Strings
expect(message).toMatch(/success/i);
expect(url).toContain("/api/v1");
// Arrays and Objects
expect(roles).toContain("admin");
expect(user).toHaveProperty("email");
expect(errors).toHaveLength(0);
Playwright Test: E2E Testing
Playwright Test is the modern standard for end-to-end browser testing in the JS/TS ecosystem.
// checkout.spec.ts
import { test, expect } from '@playwright/test';
test.describe("Checkout Flow", () => {
test("user can complete purchase", async ({ page }) => {
await page.goto("/products");
await page.click('[data-testid="add-to-cart"]');
await expect(page.locator(".cart-count")).toHaveText("1");
await page.click('[data-testid="checkout-button"]');
await page.fill('[name="card-number"]', "4111111111111111");
await page.fill('[name="expiry"]', "12/25");
await page.fill('[name="cvv"]', "123");
await page.click('[data-testid="pay-button"]');
await expect(page.locator(".confirmation")).toContainText("Order confirmed");
});
test("empty cart shows appropriate message", async ({ page }) => {
await page.goto("/cart");
await expect(page.locator(".empty-cart-message")).toBeVisible();
await expect(page.locator('[data-testid="checkout-button"]')).toBeDisabled();
});
});
Playwright Fixtures
// fixtures.ts
import { test as base } from '@playwright/test';
type TestFixtures = {
authenticatedPage: Page;
};
export const test = base.extend<TestFixtures>({
authenticatedPage: async ({ page }, use) => {
await page.goto("/login");
await page.fill('[name="email"]', "test@example.com");
await page.fill('[name="password"]', "pass123");
await page.click('[type="submit"]');
await page.waitForURL("/dashboard");
await use(page);
},
});
Cypress: Component and E2E Testing
Cypress runs in the browser alongside your application, giving it unique capabilities for frontend testing.
// checkout.cy.ts
describe("Checkout Flow", () => {
beforeEach(() => {
cy.login("test@example.com", "pass123"); // custom command
});
it("should complete purchase with valid payment", () => {
cy.visit("/products");
cy.get('[data-testid="add-to-cart"]').first().click();
cy.get(".cart-count").should("have.text", "1");
cy.get('[data-testid="checkout-button"]').click();
cy.get('[name="card-number"]').type("4111111111111111");
cy.get('[data-testid="pay-button"]').click();
cy.get(".confirmation").should("contain", "Order confirmed");
});
it("should intercept and mock payment API", () => {
cy.intercept("POST", "/api/payments", {
statusCode: 200,
body: { status: "success", transaction_id: "mock-123" }
}).as("payment");
// ... trigger payment
cy.wait("@payment");
});
});
Package Management
| Tool | Lock File | Speed | Best For |
|---|---|---|---|
| npm | package-lock.json | Standard | Default choice, comes with Node.js |
| yarn | yarn.lock | Fast | Monorepos, workspaces |
| pnpm | pnpm-lock.yaml | Fastest | Disk-efficient, strict dependency resolution |
# Initialize and install
npm init -y
npm install --save-dev jest @types/jest ts-jest typescript
npm install --save-dev @playwright/test
npm install --save-dev cypress
TypeScript Configuration for Test Projects
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist",
"rootDir": "./src",
"types": ["jest", "node"]
},
"include": ["src/**/*", "tests/**/*"]
}
Choosing Between Frameworks
| Factor | Jest | Playwright Test | Cypress |
|---|---|---|---|
| Test type | Unit, integration, API | E2E, browser automation | E2E, component |
| Execution | Node.js process | External browser control | In-browser |
| Speed | Fast (no browser) | Medium (browser launch) | Medium (browser launch) |
| Network mocking | Manual (nock, msw) | Built-in route() | Built-in intercept() |
| Debugging | Standard Node debugger | Trace viewer, screenshots | Time travel, DOM snapshots |
| CI integration | Simple | Built-in reporters | Dashboard service |
Practical Exercise
- Create a Playwright Test project that tests a public website (e.g., https://demo.playwright.dev/todomvc/)
- Write tests for: adding a todo, completing a todo, filtering active/completed
- Add TypeScript interfaces for any API response data
- Use fixtures for common setup (pre-populated todo list)
Key Takeaways
- TypeScript adds type safety that catches test bugs at compile time
- Jest for unit/API tests, Playwright for E2E, Cypress for component + E2E
- Learn at least one framework deeply; be able to read code in the others
- Use data-testid attributes for reliable element selection
- Playwright's auto-waiting eliminates most flakiness from E2E tests