JavaScript Cheat Sheet for Testers

The JavaScript a tester actually uses in Playwright, Cypress and Postman — on one page. Every result shown in the comments was checked by running the code in Node.js. For explanations and exercises, see The Complete JavaScript for Testers Guide.

Advertisement

Variables ⭐

const apiUrl = 'https://api.example.com';   // ⭐ default — can't be reassigned
let counter = 0;                              // reassignable, block-scoped
var old = 'avoid';                            // function-scoped, hoisted — don't use
  var let const
Scope Function Block Block
Reassign ✅ ✅ ❌
Use before declaration undefined ReferenceError ReferenceError

const isn't immutable — the variable can't be reassigned, but an object's properties can change (user.name = 'Alex' works; user = {} throws).

Types and Equality

'5' == 5            // true  — == converts types
'5' === 5           // false — ⭐ always use ===
typeof null         // 'object'  (a long-standing quirk)
typeof []           // 'object'  → use Array.isArray([]) → true
Number('12px')      // NaN
parseInt('12px')    // 12

undefined = declared but not assigned; null = deliberately empty.

Falsy values: false, 0, '', null, undefined, NaN — everything else, including [] and {}, is truthy.

Strings

const email = '  Asha@Example.com ';
email.trim().toLowerCase()          // 'asha@example.com'
email.includes('@')                 // true
'Order #1043'.match(/\d+/)[0]       // '1043'  — pull an ID out of text
'a,b,c'.split(',')                  // ['a', 'b', 'c']
'abc'.padStart(5, '0')              // '00abc'

const id = 7;
`User ${id} has ${id * 2} points`   // 'User 7 has 14 points' — template literal

Trimming and normalizing case before comparing UI text avoids many false failures.

Functions

function login(user, pass) { return true; }        // declaration — hoisted
const double = n => n * 2;                          // arrow, implicit return
const getUser = () => ({ id: 1 });                  // returning an object needs ( )
function search(term, limit = 10, ...filters) {}   // default + rest parameters

Arrow functions don't have their own this — which is why they're the norm in test callbacks.

Arrays ⭐

const users = [{ name: 'Asha', age: 30, active: true },
               { name: 'Ravi', age: 25, active: false }];

users.map(u => u.name)                    // ['Asha', 'Ravi']   — transform every item
users.filter(u => u.active)               // [Asha]             — keep matches
users.find(u => u.name === 'Ravi')        // Ravi's object      — first match or undefined
users.some(u => u.active)                 // true               — any?
users.every(u => u.active)                // false              — all?
users.reduce((sum, u) => sum + u.age, 0)  // 55                 — combine into one value
users.forEach(u => console.log(u.name))   // just loops, returns nothing

[3, 1, 10].sort()                          // [1, 10, 3]  ⚠️ sorts as strings!
[3, 1, 10].sort((a, b) => a - b)           // [1, 3, 10]

Also: push/pop, includes, indexOf, slice, join, and spread [...users, extra]. The sort() trap bites when checking that prices are sorted.

Objects, Destructuring and Spread

const user = { id: 1, name: 'Nav', address: { city: 'Bangalore' } };
user.address.city            // nested
user?.address?.zip           // undefined, no error — optional chaining
Object.keys(user)            // ['id', 'name', 'address']

const { name, role = 'tester', address: { city } } = { name: 'Asha', address: { city: 'Pune' } };
// name = 'Asha', role = 'tester' (default), city = 'Pune'

const [first, ...rest] = [10, 20, 30];   // first = 10, rest = [20, 30]

const base = { env: 'qa', retries: 1 };
{ ...base, retries: 2 }                  // { env: 'qa', retries: 2 } — copy and override

Playwright fixtures use destructuring constantly: async ({ page, request }) => { … }.

JSON — the Tester's Daily Bread

JSON.parse(responseText);        // string → object
JSON.stringify(payload);         // object → string
JSON.stringify(obj, null, 2);    // pretty-printed

try { JSON.parse('{bad json}'); } catch (e) { e.name; }   // 'SyntaxError'

Null-Safe Operators

const city = user?.address?.city;          // optional chaining
0 || 'default'                             // 'default' — || replaces ANY falsy value
0 ?? 'default'                             // 0         — ?? replaces only null/undefined
const msg = status === 200 ? 'OK' : 'Failed';   // ternary

Use ?? for defaults when 0 or '' are valid values (a quantity of 0, an empty search).

Loops

for (let i = 0; i < 5; i++) {}
for (const u of users) {}        // ⭐ values of an array
for (const k in obj) {}          // keys of an object — not for arrays

Classes (Page Objects)

class LoginPage {
  constructor(page) { this.page = page; }
  async open() { await this.page.goto('/login'); }
}

Async JavaScript ⭐⭐

The most important section for automation: browser actions and API calls are asynchronous and return Promises. Forget await and the next line runs before the action finishes.

// Promise states: pending → fulfilled | rejected
async function getUser(id) {
  try {
    const res = await fetch(`/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error('Request failed:', err.message);
    throw err;
  }
}

// Run independent calls in parallel
const [a, b] = await Promise.all([getUser(1), getUser(2)]);   // two 100 ms waits → ~100 ms total
// ❌ Flaky — missing await
page.click('#submit');
expect(page.locator('#msg')).toBeVisible();

// ✅ Correct
await page.locator('#submit').click();
await expect(page.locator('#msg')).toBeVisible();

Rules: await every Playwright action and web-first assertion; only use await inside async functions (or ES modules); don't use forEach with async callbacks — use for…of or Promise.all.

More: Playwright Auto-Wait.

Modules

export class LoginPage { … }                  // pages/LoginPage.js
import { LoginPage } from '../pages/LoginPage'; // tests/login.spec.js

Where You'll Use It

Tool Where JavaScript appears Example
Playwright The whole test — async/await everywhere await expect(page).toHaveURL(/dashboard/)
Cypress Test code and chained commands cy.get('#msg').should('be.visible')
Postman Pre-request and Tests tabs pm.expect(pm.response.json().id).to.be.a('number')
Selenium JavaScriptExecutor snippets arguments[0].scrollIntoView({block: 'center'})
Browser DevTools Console checks and quick scripts $$('a').length

See Postman, Playwright and JavaScriptExecutor for more.

Testing yourself before an interview? JavaScript questions come up in the Playwright Interview Simulator, and JavaScriptExecutor questions in the Selenium Interview Simulator.

From Real Projects

My automation background is Java with Selenium and TestNG. JavaScript comes up more and more for testers because tools like Playwright and Cypress are built on it — and most concepts on this page have a direct Java equivalent. Learning it alongside Java makes switching between frameworks much easier. Focus on async/await first — it's behind most JavaScript test issues. Further reading: JavaScript Variables.

📚 Official documentation: MDN: JavaScript Guide · dev.java: Learn Java (Oracle)

FAQs

How much JavaScript does a tester need?

Variables, strings, arrays, objects, JSON and — above all — Promises and async/await. That covers Playwright, Cypress and Postman.

Why do my Playwright tests fail randomly?

Often a missing await, or a one-shot value check instead of a web-first assertion. Await every action and use expect(locator) assertions.

== or ===?

Always ===. == converts types first, so '5' == 5 is true.

Why does [3, 1, 10].sort() give [1, 10, 3]?

Without a comparator, sort() compares values as strings. Use sort((a, b) => a - b) for numbers.

|| or ?? for defaults?

?? when 0 or '' are valid values; || replaces any falsy value.

Go Deeper