SoftwareTestPilot
Module 02 · Lesson 1beginner 18 min read JavaScript

JavaScript for Testers — The 20% That Powers Every Test

Skip the front-end boot camp. Learn the exact JavaScript and TypeScript features every Playwright, Cypress, and WebdriverIO test uses — with runnable examples flavoured for QA.

Quick answer

You need ~20% of JavaScript: const/let, arrays, objects, arrow functions, destructuring,async/await, and ES modules. TypeScript adds tiny type annotations on top. That is enough to read, write and debug any Playwright test in production.

1. Variables and types

// Use const by default; let only when you reassign
const url = 'https://example.com';
let attempts = 0;
attempts = attempts + 1;

// Primitive types
const name: string = 'Ada';
const price: number = 19.99;
const inStock: boolean = true;
const missing: null = null;
const nothing: undefined = undefined;
Never use var
var has function scope and hoists in weird ways. const and let are block-scoped like every other modern language.

2. Arrays and objects

// Array of test data
const users = ['ada@a.co', 'lin@b.co', 'ken@c.co'];
users.length;                 // 3
users[0];                     // 'ada@a.co'
users.push('new@d.co');       // add
users.includes('lin@b.co');   // true

// The three you'll use constantly
users.map(u => u.toUpperCase());
users.filter(u => u.endsWith('.co'));
users.find(u => u.startsWith('ken'));

// Object = key/value bag (a JSON row)
const product = {
  id: 42,
  title: 'Blue Widget',
  price: 9.99,
  tags: ['new', 'sale'],
};
product.title;               // 'Blue Widget'
product['price'];            // 9.99
Object.keys(product);        // ['id','title','price','tags']

3. Functions and arrow functions

// Classic function
function add(a: number, b: number) {
  return a + b;
}

// Arrow function — same thing, shorter
const add2 = (a: number, b: number) => a + b;

// Multi-line arrow
const login = async (page, email: string, password: string) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(email);
  await page.getByLabel('Password').fill(password);
  await page.getByRole('button', { name: 'Sign in' }).click();
};

4. Destructuring and spread

// Every Playwright test file starts with this
import { test, expect } from '@playwright/test';

// Object destructuring in test callbacks
test('checkout works', async ({ page, request, context }) => {
  // page, request, context pulled from fixtures object
});

// Array destructuring
const [first, second] = ['a', 'b', 'c'];

// Spread — clone and extend
const base = { headless: true };
const opts = { ...base, slowMo: 100 };

5. async / await — the heart of every test

Browser and network calls return promises. await pauses execution until the promise settles.

test('waits properly', async ({ page }) => {
  await page.goto('/');                              // 1. wait for nav
  await page.getByRole('link', { name: 'Docs' }).click();  // 2. wait for click
  await expect(page).toHaveURL(/docs/);              // 3. wait for URL
});

// Parallel awaits — start both, wait for both
const [resp1, resp2] = await Promise.all([
  page.waitForResponse('**/api/user'),
  page.getByRole('button', { name: 'Refresh' }).click(),
]);
Missing await = flaky test
Forgetting await is the #1 source of intermittent failures. The test moves on before the action finishes. Enable the ESLint rule @typescript-eslint/no-floating-promises.

6. Modules and npm

// lib/api.ts — export
export async function createUser(email: string) { /* ... */ }
export const BASE_URL = 'https://api.example.com';

// tests/user.spec.ts — import
import { createUser, BASE_URL } from '../lib/api';
import { test } from '@playwright/test';   // 3rd-party from npm

test('signs up a user', async () => {
  await createUser('demo@example.com');
});
  • npm install foo — add a dependency to package.json.
  • npm run test — run a script defined in package.json.
  • npx playwright test — run a CLI shipped by a package without installing it globally.

7. TypeScript — the 5 syntaxes you'll actually use

SyntaxMeaningExample
: stringVariable is a stringconst name: string = 'Ada'
{ email: string }Object shapefunction login(u: { email: string; pwd: string })
string[]Array of stringsconst tags: string[] = []
?Optional{ nickname?: string }
type / interfaceReusable shapetype User = { id: number }

8. Applied to a real Playwright test

tests/checkout.spec.ts
ts
import { test, expect } from '@playwright/test';

type Product = { name: string; qty: number };

const items: Product[] = [
  { name: 'Blue Widget', qty: 2 },
  { name: 'Red Gadget', qty: 1 },
];

test('adds multiple items to cart', async ({ page }) => {
  await page.goto('/shop');

  for (const { name, qty } of items) {
    const card = page.getByRole('article', { name });
    for (let i = 0; i < qty; i++) {
      await card.getByRole('button', { name: 'Add to cart' }).click();
    }
  }

  const totalItems = items.reduce((sum: number, i: Product) => sum + i.qty, 0);
  await expect(page.getByTestId('cart-badge')).toHaveText(String(totalItems));
});

9. Common mistakes to avoid

Forgetting await inside loops

Use for...of with await, not .forEach()forEach does not respect promises.

Using == instead of ===

Always use strict equality ===. Loose == does type coercion (0 == '' is true).

Mutating shared state

Test isolation matters. Don't push to a module-level array from inside a test — create fresh data per test.

10. Hands-on task (20 minutes)

  1. 1

    Open a Node REPL

    Run node in your terminal. Try [1,2,3].map(n => n * 2) and { a: 1, ...{ b: 2 } }.

  2. 2

    Write a helper

    Create lib/data.ts exporting randomEmail() that returns `test+${Date.now()}@example.com`. Import it in a spec.

  3. 3

    Refactor a test with destructuring

    Take a test that reads user.email and user.password. Rewrite as const { email, password } = user.

  4. 4

    Loop through datasets

    Given an array of 3 test users, write a for...of that logs each in and asserts a dashboard heading.

Ready for real locators? Continue to Module 3 / Lesson 1 — Playwright Locators.

Frequently asked questions

1.Do I really need to learn JavaScript to do automation?
Yes. Every modern automation framework — Playwright, Cypress, WebdriverIO, TestCafe — is JavaScript/TypeScript first. Even Selenium bindings for Node are the fastest-growing. You do not need to be a full-stack dev; you need ~20% of the language.
2.JavaScript or TypeScript for Playwright?
TypeScript. It ships out of the box with `npm init playwright`, gives you autocomplete on the entire Playwright API, and catches typos before you run the test. The syntax you actually write is 95% the same as JavaScript.
3.How long does it take a manual tester to learn enough JavaScript?
Two focused weekends. Cover variables, arrow functions, arrays (map/filter/find), objects and destructuring, async/await, and modules. That is enough to read and write any Playwright test in production.
4.What is async/await and why does every test use it?
Browser actions are asynchronous — clicks, navigations and network calls take time. `await` pauses your test until the promise settles, so you can write top-to-bottom code that still respects real timing. Skip `await` and you get flaky tests and race conditions.
5.Do I need to learn Node.js too?
Only the basics: `npm install`, `package.json`, `import/export`, and how scripts run. You do not need Express, streams, or backend Node APIs.
6.Which IDE should I use?
VS Code with the Playwright, ESLint and Prettier extensions. It gives you inline errors, one-click test debugging, and the Pick Locator tool from Playwright.

Related lessons