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 let2. 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