SoftwareTestPilot
Module 02 · Lab 1
Beginner
11 min read

JavaScript for Testers — The Only 20% You Actually Need

A practical crash course in the JavaScript every QA tester uses every day in Playwright.

1. Variables

Use const for almost everything in tests. Use let only when you need to reassign. Avoid var.

const baseUrl = 'https://softwaretestpilot.com';
const expectedTitle = /SoftwareTestPilot/;

let attempts = 0;
attempts = attempts + 1; // reassignment requires let

2. Functions & arrow functions

Arrow functions (() => {}) are concise and don't rebind this. Playwright tests use them everywhere.

// Arrow function — preferred
const login = async (page, user) => {
  await page.getByLabel('Email').fill(user.email);
  await page.getByLabel('Password').fill(user.password);
  await page.getByRole('button', { name: 'Sign in' }).click();
};

// Equivalent classic function
async function loginClassic(page, user) {
  await page.getByLabel('Email').fill(user.email);
}

3. Arrays & objects

.map, .filter, and .find are how you shape test data — no for loops needed.

const users = [
  { name: 'Alice', role: 'admin' },
  { name: 'Bob',   role: 'user'  },
  { name: 'Cara',  role: 'admin' },
];

const admins   = users.filter(u => u.role === 'admin');
const names    = users.map(u => u.name);
const findBob  = users.find(u => u.name === 'Bob');

4. Promises & async/await

Every Playwright action returns a Promise. Always await it — forgetting await is the #1 reason tests "pass" when they shouldn't.

// ✅ Correct
test('opens dashboard', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page.getByRole('heading')).toHaveText('Dashboard');
});

// ❌ Wrong — assertion runs before navigation completes
test('broken', async ({ page }) => {
  page.goto('/dashboard');
  expect(page.getByRole('heading')).toHaveText('Dashboard');
});

5. Destructuring

Pull fields out of objects in one line. This is how Playwright fixtures are unpacked.

// Fixture destructuring
test('api + browser', async ({ page, request }) => {
  const apiContext = await request.newContext();
  // ...
});

// Object destructuring for test data
const { email, password } = users[0];

6. Template literals

Backticks let you interpolate variables into strings — perfect for dynamic selectors and URLs.

const id = 42;
await page.goto(`/orders/${id}`);
await expect(page.locator(`[data-order-id="${id}"]`)).toBeVisible();

7. Hands-on task

Refactor this hard-coded test to use a config object so the same test can run against staging or production by changing one variable.

Before

test('login works', async ({ page }) => {
  await page.goto('https://staging.softwaretestpilot.com/login');
  await page.getByLabel('Email').fill('qa@example.com');
  await page.getByLabel('Password').fill('Hunter2!');
  await page.getByRole('button', { name: 'Sign in' }).click();
});

After

const env = {
  baseUrl: process.env.BASE_URL ?? 'https://staging.softwaretestpilot.com',
  user:    { email: 'qa@example.com', password: 'Hunter2!' },
};

test('login works', async ({ page }) => {
  await page.goto(`${env.baseUrl}/login`);
  await page.getByLabel('Email').fill(env.user.email);
  await page.getByLabel('Password').fill(env.user.password);
  await page.getByRole('button', { name: 'Sign in' }).click();
});

8. What's next

Continue to Module 03: Playwright Locators & actions.

Up next in the learning path

Click, fill, select