Playwright Beginner Mistakes to Avoid: 18 Common Errors and Best Practices

Introduction: Why Beginners Make Playwright Mistakes

Playwright is powerful, but beginners can still create unreliable automation if they carry old Selenium habits into a Playwright project.

The good news is that most Playwright beginner mistakes to avoid are easy to recognize once you understand how Playwright is designed to work.

Common problems include using fragile selectors, adding unnecessary hard waits, sharing test data, skipping assertions, creating overly complicated frameworks, and using retries to hide flaky tests.

For QA Automation Engineers and SDETs, avoiding these problems is important. A test suite should not simply pass on a developer’s laptop. It should remain reliable when executed in parallel, across browsers, and inside CI/CD.

This guide explains the most important Playwright beginner mistakes, including incorrect and correct TypeScript examples.


Quick Overview: Common Playwright Mistakes

MistakeTypical ProblemBetter Practice
Unreliable locatorsTests break after UI changesUse semantic locators
Hard waitsSlow/flaky testsUse auto-waiting
Weak assertionsFalse confidenceAssert business outcomes
Repeated loginSlow test suiteReuse authentication state
Shared stateTests affect each otherIsolate tests
Hard-coded credentialsSecurity riskUse environment variables
No tracingDifficult debuggingEnable traces/screenshots
One browser onlyMissed compatibility bugsTest required browser matrix
Hidden flaky testsCI failuresInvestigate root causes
Over-engineeringDifficult maintenanceStart simple

Understanding these issues is the foundation of Playwright best practices for beginners.


Mistake 1: Skipping Playwright Installation and Project Configuration

One of the first Playwright mistakes beginners make is immediately creating test files without understanding the project configuration.

A properly initialized project provides the test runner, configuration, TypeScript support, browser projects, and other useful defaults.

❌ Incorrect Approach

Installing only a random package and manually creating an incomplete project.

Why It Fails

You may encounter:

  • Missing browser binaries
  • Incorrect test discovery
  • TypeScript configuration problems
  • Missing Playwright Test features
  • Inconsistent local and CI environments

✅ Correct Approach

Initialize a Playwright project:

npm init playwright@latest

Install browsers:

npx playwright install

For Linux CI environments:

npx playwright install –with-deps

Best Practice

Keep package.json, playwright.config.ts, tsconfig.json, and your test directories under source control.


Mistake 2: Using Unreliable Locators

Locators are one of the biggest sources of automation instability.

❌ Incorrect Approach

await page.locator(‘div.container > div:nth-child(2) button’).click();

Why It Fails

The selector depends on the DOM structure.

If developers add another <div>, change the layout, or reorder elements, your test may break.

✅ Correct Approach

await page.getByRole(‘button’, { name: ‘Login’ }).click();

Or:

await page.getByLabel(‘Username’).fill(‘testuser’);

Best Practice

Prefer locators based on how users interact with the application:

page.getByRole()

page.getByLabel()

page.getByText()

page.getByPlaceholder()

Use locator() when a CSS selector or another locator strategy is genuinely appropriate.


Mistake 3: Overusing CSS and XPath Selectors

Selenium engineers transitioning to Playwright often continue using CSS and XPath for everything.

❌ Incorrect Approach

await page.locator(‘//button[@id=”login”]’).click();

Or:

await page.locator(‘div.login-panel button.login’).click();

Why It Fails

These selectors can become tightly coupled to implementation details.

✅ Correct Approach

await page.getByRole(‘button’, { name: ‘Login’ }).click();

If there is no suitable semantic locator:

await page.locator(‘[data-testid=”login-button”]’).click();

Best Practice

A practical locator priority is:

  1. getByRole()
  2. getByLabel()
  3. getByPlaceholder()
  4. getByText()
  5. Test IDs
  6. CSS/XPath when necessary

Do not avoid CSS completely. Avoid unnecessary dependence on fragile CSS or XPath.


Mistake 4: Using waitForTimeout() Unnecessarily

This is one of the most common Playwright automation mistakes.

❌ Incorrect Approach

await page.click(‘#login’);

await page.waitForTimeout(5000);

await expect(page.getByText(‘Dashboard’)).toBeVisible();

Why It Fails

The application might be ready after one second or might require seven seconds.

A fixed five-second delay does not understand application state.

✅ Correct Approach

await page.getByRole(‘button’, { name: ‘Login’ }).click();

await expect(

  page.getByRole(‘heading’, { name: ‘Dashboard’ })

).toBeVisible();

Or when navigation is the actual condition:

await page.getByRole(‘button’, { name: ‘Login’ }).click();

await page.waitForURL(‘**/dashboard’);

Best Practice

Use meaningful synchronization rather than sleeping.

Avoid:

await page.waitForTimeout(5000);

unless you have a specific debugging or exceptional use case.


Mistake 5: Not Understanding Playwright Auto-Waiting

Beginners sometimes add waits because they assume Playwright behaves like a basic browser-control library.

Playwright automatically waits for many actionability conditions before performing actions.

❌ Incorrect Approach

await page.waitForTimeout(3000);

await page.getByRole(‘button’, { name: ‘Submit’ }).click();

Why It Fails

The test becomes slower without necessarily becoming more reliable.

✅ Correct Approach

await page.getByRole(‘button’, { name: ‘Submit’ }).click();

Playwright can wait for the element to become actionable.

Assertions also provide useful synchronization:

await expect(page.getByText(‘Order placed’)).toBeVisible();

Best Practice

Learn Playwright’s auto-waiting model before adding manual synchronization.


Mistake 6: Writing Weak or Missing Assertions

A test that performs actions without validating the result provides little value.

❌ Incorrect Approach

await page.goto(‘/login’);

await page.getByLabel(‘Username’).fill(‘admin’);

await page.getByLabel(‘Password’).fill(‘password’);

await page.getByRole(‘button’, { name: ‘Login’ }).click();

The test could pass even if login failed.

Why It Fails

There is no business validation.

✅ Correct Approach

await page.getByRole(‘button’, { name: ‘Login’ }).click();

await expect(page).toHaveURL(/dashboard/);

await expect(

  page.getByRole(‘heading’, { name: ‘Dashboard’ })

).toBeVisible();

Best Practice

Ask:

“What business outcome does this test prove?”

Then assert that outcome.


Mistake 7: Repeating Login Steps in Every Test

Imagine 100 tests that all perform login.

❌ Incorrect Approach

test(‘test 1’, async ({ page }) => {

  await login(page);

  // test

});

test(‘test 2’, async ({ page }) => {

  await login(page);

  // test

});

Why It Fails

It makes the suite:

  • Slower
  • More repetitive
  • Harder to maintain

✅ Correct Approach: Authentication State

Playwright can reuse authenticated browser state.

For example, a setup project can create a storage state:

await page.goto(‘/login’);

await page.getByLabel(‘Username’).fill(

  process.env.TEST_USERNAME!

);

await page.getByLabel(‘Password’).fill(

  process.env.TEST_PASSWORD!

);

await page.getByRole(‘button’, { name: ‘Login’ }).click();

await page.context().storageState({

  path: ‘playwright/.auth/user.json’

});

Then configure:

use: {

  storageState: ‘playwright/.auth/user.json’

}

Best Practice

Reuse authentication state when the scenario does not specifically test login itself.


Mistake 8: Ignoring Test Isolation

Each test should ideally be independent.

❌ Incorrect Approach

test(‘create customer’, async ({ page }) => {

  // creates customer

});

test(‘delete customer created above’, async ({ page }) => {

  // assumes previous test succeeded

});

Why It Fails

If the first test fails, the second test also fails.

Parallel execution makes this even more problematic.

✅ Correct Approach

Each test should create or obtain the data it needs.

test(‘delete customer’, async ({ page }) => {

  // Arrange required customer

  // Delete customer

  // Assert deletion

});

Best Practice

Tests should be independently runnable:

npx playwright test -g “delete customer”

If a test cannot run independently, investigate the dependency.


Mistake 9: Sharing Pages or Test Data Incorrectly

❌ Incorrect Approach

Creating one global page:

let page: Page;

beforeAll(async ({ browser }) => {

  page = await browser.newPage();

});

Then allowing multiple tests to modify it.

Why It Fails

Tests can interfere with each other.

✅ Correct Approach

Use Playwright’s built-in fixtures:

test(‘customer test’, async ({ page }) => {

  await page.goto(‘/customers’);

});

Playwright creates isolated test environments through its fixture system.

Best Practice

Avoid global mutable browser state.


Mistake 10: Not Using Fixtures Effectively

Fixtures help provide reusable test dependencies.

❌ Incorrect Approach

Copying setup into every test:

test(‘test 1’, async ({ page }) => {

  // repeated setup

});

test(‘test 2’, async ({ page }) => {

  // same setup

});

✅ Correct Approach

Create a custom fixture:

import { test as base } from ‘@playwright/test’;

type Fixtures = {

  loggedInPage: void;

};

export const test = base.extend<Fixtures>({

  loggedInPage: async ({ page }, use) => {

    await page.goto(‘/login’);

    await page.getByLabel(‘Username’)

      .fill(process.env.TEST_USERNAME!);

    await page.getByLabel(‘Password’)

      .fill(process.env.TEST_PASSWORD!);

    await page.getByRole(‘button’, { name: ‘Login’ }).click();

    await use();

  }

});

Best Practice

Use fixtures for reusable setup, authentication, test data, and service dependencies.

Do not create fixtures merely to make simple code look sophisticated.


Mistake 11: Avoiding Page Object Model in Larger Projects

A beginner may put everything into test files.

❌ Incorrect Approach

test(‘login’, async ({ page }) => {

  await page.goto(‘/login’);

  await page.getByLabel(‘Username’).fill(‘admin’);

  await page.getByLabel(‘Password’).fill(‘password’);

  await page.getByRole(‘button’, { name: ‘Login’ }).click();

});

This is acceptable for a tiny test.

The problem appears when dozens of tests repeat the same workflow.

✅ Correct Approach

Create a page object:

import { Page } from ‘@playwright/test’;

export class LoginPage {

  constructor(private page: Page) {}

  async login(username: string, password: string) {

    await this.page.getByLabel(‘Username’).fill(username);

    await this.page.getByLabel(‘Password’).fill(password);

    await this.page.getByRole(‘button’, { name: ‘Login’ }).click();

  }

}

Then:

const loginPage = new LoginPage(page);

await loginPage.login(

  process.env.TEST_USERNAME!,

  process.env.TEST_PASSWORD!

);

Best Practice

Use Page Object Model when the application and test suite are large enough to benefit from abstraction.

Do not create a 50-class framework for five tests.


Mistake 12: Hard-Coding Credentials and Sensitive Data

❌ Incorrect Approach

await page.getByLabel(‘Username’).fill(‘admin’);

await page.getByLabel(‘Password’).fill(‘MyRealPassword123’);

Why It Fails

Credentials can accidentally enter:

  • Git repositories
  • Pull requests
  • Logs
  • Screenshots
  • CI artifacts

✅ Correct Approach

Use environment variables:

const username = process.env.TEST_USERNAME;

const password = process.env.TEST_PASSWORD;

if (!username || !password) {

  throw new Error(‘Test credentials are missing’);

}

await page.getByLabel(‘Username’).fill(username);

await page.getByLabel(‘Password’).fill(password);

Then configure credentials in your local environment or CI secret store.

Best Practice

Never commit production passwords, API keys, tokens, or other secrets to source control.


Mistake 13: Ignoring Screenshots, Traces, and Reports

A failed test without diagnostic evidence can take much longer to troubleshoot.

❌ Incorrect Approach

Running:

npx playwright test

and looking only at the final error message.

✅ Correct Approach

Configure:

use: {

  screenshot: ‘only-on-failure’,

  video: ‘on-first-retry’,

  trace: ‘on-first-retry’

}

Then run:

npx playwright test

For the HTML report:

npx playwright show-report

Best Practice

Use traces and screenshots strategically, especially for CI failures.

Trace Viewer can help identify the exact action, page state, and timing around a failure.


Mistake 14: Running Tests Only in One Browser

❌ Incorrect Approach

Only testing Chromium:

npx playwright test –project=chromium

Why It Fails

Your application may have browser-specific behavior.

✅ Correct Approach

Configure browser projects:

projects: [

  {

    name: ‘chromium’,

    use: { …devices[‘Desktop Chrome’] }

  },

  {

    name: ‘firefox’,

    use: { …devices[‘Desktop Firefox’] }

  },

  {

    name: ‘webkit’,

    use: { …devices[‘Desktop Safari’] }

  }

]

Best Practice

Do not blindly run every test on every browser.

Choose the browser matrix according to your application’s supported browsers and risk.


Mistake 15: Ignoring Parallel Execution and CI/CD Differences

A test that works locally may fail in CI.

Common causes

❌ Incorrect Approach

Assuming:

Local = CI

✅ Correct Approach

Run tests similarly to CI:

npx playwright test

Use isolated test data and avoid order dependencies.

A CI workflow can install dependencies and browsers:

– run: npm ci

– run: npx playwright install –with-deps

– run: npx playwright test

Best Practice

Treat CI as a first-class execution environment, not an afterthought.


Mistake 16: Using Retries to Hide Flaky Tests

Retries are useful, but they should not become a permanent solution for unstable automation.

❌ Incorrect Approach

export default defineConfig({

  retries: 5

});

and assuming the suite is reliable because most failures eventually pass.

Why It Fails

Retries can hide:

  • Race conditions
  • Bad locators
  • Application defects
  • Environment problems
  • Test-data collisions

✅ Correct Approach

Use a small retry count in CI and investigate failures.

export default defineConfig({

  retries: process.env.CI ? 2 : 0

});

Best Practice

A retry is a diagnostic safety net, not a replacement for fixing flaky tests.


Mistake 17: Not Handling Dynamic Elements Correctly

Modern applications frequently load content asynchronously.

❌ Incorrect Approach

await page.waitForTimeout(3000);

await page.locator(‘.result’).click();

Why It Fails

The result may take one second or ten seconds.

✅ Correct Approach

Use a locator and assertion:

const result = page.getByRole(‘link’, {

  name: ‘Product A’

});

await expect(result).toBeVisible();

await result.click();

Best Practice

Synchronize against meaningful application state rather than arbitrary time.


Mistake 18: Creating an Overly Complex Framework Too Early

This is one of the most subtle Playwright beginner mistakes to avoid.

Beginners sometimes create:

  • Multiple utility layers
  • Huge fixture hierarchies
  • Excessive abstraction
  • Complex factories
  • Dozens of page classes
  • Custom reporting systems

before writing enough tests to understand the application.

❌ Incorrect Approach

Building an enterprise framework before understanding basic Playwright APIs.

✅ Correct Approach

Start with:

tests/

pages/

playwright.config.ts

Then add fixtures, utilities, test data, and services as real needs appear.

Best Practice

Build the simplest framework that solves the current problem.

Good framework design evolves with the test suite.


Common Playwright Errors Caused by Beginner Mistakes

ErrorLikely CauseSolution
Locator timeoutWrong/fragile locatorInspect and improve locator
Element not visibleIncorrect state assumptionAssert expected state
Navigation timeoutSlow/unavailable environmentCheck URL/network/application
Browser executable missingBrowsers not installednpx playwright install
Test passes locally, fails CIEnvironment/state issueReproduce CI conditions
Random timeoutPoor synchronizationRemove hard waits
Test works alone, fails in suiteShared stateImprove isolation
Tests fail after UI changeFragile selectorsUse semantic locators
Tests pass after retriesFlakinessFind root cause

Recommended Playwright Project Structure

A growing Playwright TypeScript project can use:

playwright-project/

├── tests/

│   ├── login.spec.ts

│   ├── checkout.spec.ts

│   └── search.spec.ts

├── pages/

│   ├── LoginPage.ts

│   ├── CheckoutPage.ts

│   └── SearchPage.ts

├── fixtures/

│   └── testFixtures.ts

├── utils/

│   └── testData.ts

├── playwright.config.ts

├── package.json

└── tsconfig.json

This structure is not mandatory.

Use it when your project becomes large enough to justify separation.


Playwright Best Practices Checklist

Before considering your Playwright suite reliable, check:


Playwright Interview Questions Based on Common Mistakes

1. Why should you avoid waitForTimeout()?

Because fixed waits do not synchronize with application state. They can make tests slow and flaky.

2. What locators should beginners prefer?

Prefer user-facing and semantic locators such as getByRole() and getByLabel() where appropriate.

3. Does Playwright automatically wait for elements?

Playwright performs automatic waiting for many actionability conditions, and its web-first assertions can wait for expected states.

4. Why is test isolation important?

Independent tests can run reliably in different orders and in parallel without modifying each other’s state.

5. Should retries be used?

Yes, limited retries can be useful in CI, but they should not be used to hide flaky tests.

6. Why use environment variables?

They prevent credentials and other sensitive configuration from being hard-coded into source code.

7. When should you use Page Object Model?

Use it when the test suite becomes large enough that reusable page behavior improves maintainability.

8. How would you investigate a test that fails only in CI?

Check the trace, screenshot, video, logs, browser version, environment variables, test data, timing assumptions, and resource constraints.

This type of troubleshooting question is common in SDET and senior QA interviews because it tests practical framework knowledge rather than command memorization.


Playwright Learning Roadmap for Beginners

If you are learning Playwright for beginners, follow this progression:

Phase 1: Fundamentals

Learn:

Phase 2: UI Automation

Learn:

  • Locators
  • Clicks
  • Forms
  • Dropdowns
  • Checkboxes
  • Keyboard and mouse
  • Auto-waiting
  • Assertions

Phase 3: Framework Development

Learn:

  • Fixtures
  • Page Object Model
  • Authentication
  • Test data
  • Configuration
  • Environment variables

Phase 4: Advanced Automation

Learn:

Phase 5: DevOps

Learn:

Related learning topics include Playwright Basics, Playwright Installation Guide, Playwright First Test Script, Playwright Getting Started Guide, Playwright Simple Example, Playwright Basic Commands, Playwright Learning Roadmap, Playwright TypeScript Tutorial, Playwright Locators, Playwright Auto Waiting, Playwright Page Object Model, Playwright Fixtures Tutorial, Playwright Data Driven Testing Tutorial, Playwright API Testing, Playwright Authentication Tutorial, Playwright Reporting Tutorial, Playwright Parallel Execution Tutorial, Playwright CI/CD Tutorial, Playwright GitHub Actions Tutorial, Playwright Docker Tutorial, Playwright Troubleshooting, and Playwright Interview Questions.


FAQs: Playwright Beginner Mistakes to Avoid

What are the most common Playwright mistakes?

The most common mistakes include using fragile locators, relying on hard waits, skipping assertions, sharing state between tests, hard-coding credentials, ignoring traces, and using retries to hide flaky tests.

How do I avoid Playwright test failures?

Use reliable locators, Playwright’s auto-waiting, meaningful assertions, independent test data, proper fixtures, diagnostic artifacts, and CI-compatible configuration.

Should beginners use XPath in Playwright?

XPath is supported, but it should not automatically be the first choice. Prefer stable, user-facing locators when possible.

Is waitForTimeout() bad in Playwright?

It is not inherently invalid, but it is usually a poor synchronization strategy for production tests because a fixed delay does not represent application readiness.

Why does my Playwright test pass locally but fail in CI?

Common causes include environment differences, missing variables, resource limitations, browser differences, shared state, test-data collisions, and timing assumptions.

Should I use Page Object Model in every Playwright project?

No. POM is useful when it improves maintainability. A small project may not need a complex abstraction layer.

How can I debug flaky Playwright tests?

Start with the HTML report, trace, screenshots, video, console output, and CI logs. Then determine whether the root cause is the locator, application state, test data, environment, or synchronization.

Leave a Comment

Your email address will not be published. Required fields are marked *