QA Engineer Skills 2026QA-2026JavaScript and TypeScript for QA

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

  1. Create a Playwright Test project that tests a public website (e.g., https://demo.playwright.dev/todomvc/)
  2. Write tests for: adding a todo, completing a todo, filtering active/completed
  3. Add TypeScript interfaces for any API response data
  4. 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