Introduction
One of the most common situations for an experienced SDET is joining a company where the Playwright automation framework has already been built. You open the repository and are met with hundreds or thousands of files.
playwright-framework/
│
├── tests/
│ ├── ui/
│ ├── api/
│ ├── integration/
│ └── e2e/
│
├── pages/
├── components/
├── fixtures/
├── services/
├── clients/
├── utils/
├── test-data/
├── schemas/
├── config/
├── auth/
│
├── playwright.config.ts
├── package.json
├── tsconfig.json
└── README.mdThe wrong approach is the copy-paste shortcut:
Open tests/
↓
Find something similar
↓
Copy the test
↓
Change a few locators
↓
CommitYou may produce a working test, but you still do not understand the framework. A professional SDET builds understanding in a deliberate order.
Application
↓
Test Execution
↓
Framework Architecture
↓
Fixtures & Dependencies
↓
Authentication
↓
Page / Service Layers
↓
Test Data
↓
API / DB / Messaging
↓
Reporting
↓
CI/CD
↓
New Test DevelopmentThis tutorial shows exactly how to do that.
1. Understand the Application Before the Automation
Before studying Playwright code, understand what the application actually does.
- Who uses the application?
- What are the major business workflows?
- What user roles exist?
- What are the important pages?
- What APIs support those pages?
- Which databases or backend services are involved?
- Are there asynchronous systems such as Kafka or WebSockets?
- Which business processes are considered critical?
For example, imagine an e-commerce system:
Customer
↓
React UI
↓
Product API
↓
Order Service
↓
Kafka
↓
Payment Service
↓
PostgreSQLIf your Playwright test interacts with this application, understanding only the UI is not enough. An enterprise framework may validate UI, REST API, GraphQL, database state, WebSocket messages, Kafka events and cloud services in a single scenario.
2. First File to Open: README.md
The first file I normally inspect is the README. It should explain how the framework is intended to be used.
Project purpose
Installation
Prerequisites
Environment setup
Running tests
Authentication
Test categories
Reporting
CI/CD
Debugging
Coding standardsnpm install
npx playwright test
npm run test:ui
npm run test:api
npm run test:stage
npm run test:regression3. Second File: package.json
package.json quickly tells you what technologies the framework uses. Focus on scripts, dependencies and devDependencies.
{
"scripts": {
"test": "playwright test",
"test:ui": "playwright test tests/ui",
"test:api": "playwright test tests/api",
"test:stage": "cross-env ENV=stage playwright test",
"test:smoke": "playwright test --grep @smoke",
"test:regression": "playwright test --grep @regression"
}
}Now look at the dependencies:
{
"devDependencies": {
"@playwright/test": "...",
"allure-playwright": "...",
"dotenv": "...",
"ajv": "...",
"pg": "...",
"kafkajs": "...",
"ws": "..."
}
}From this alone you can infer the framework's scope:
Playwright UI Testing
REST API Testing
Schema Validation
PostgreSQL Validation
Kafka Testing
WebSocket Testing
Allure Reporting
Environment Management4. Third File: playwright.config.ts
This is one of the most important files in any Playwright project — it controls how the test runner behaves: test directories, retries, workers, reporters, projects, timeouts, global setup/teardown and browser context options.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 60_000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: [
['html'],
['allure-playwright']
],
use: {
baseURL: process.env.BASE_URL,
storageState: 'playwright/.auth/user.json',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
}
]
});While reading this file, answer these questions:
- Where are tests located and which environment is used?
- Which browsers run, how many workers, and are tests parallel?
- Are retries enabled and what is the default timeout?
- Which reporters are configured?
- How is authentication loaded?
- Are screenshots, videos and traces captured?
- Is there a global setup, and are there multiple projects?
5. Understand the Folder Architecture
Stop reading individual files for a moment and look at the complete repository structure.
project/
│
├── tests/
│ ├── ui/
│ ├── api/
│ │ ├── rest/
│ │ └── graphql/
│ ├── websocket/
│ ├── messaging/
│ └── e2e/
│
├── pages/
├── components/
├── fixtures/
├── clients/
├── services/
├── utils/
├── test-data/
├── schemas/
├── config/
├── auth/
└── scripts/A typical interpretation of each layer:
- tests/ — test scenarios
- pages/ — Page Objects
- components/ — reusable UI components
- fixtures/ — test dependencies and setup
- clients/ — REST, GraphQL, WebSocket or messaging clients
- services/ — business-level operations
- utils/ — technical reusable utilities
- test-data/ — test data, builders or factories
- schemas/ — API and event schema validation
- config/ — environment configuration
- auth/ — authentication and session management
- scripts/ — supporting execution and setup scripts
At this stage you are creating a mental map:
Test
↓
Fixture
↓
Page / Service
↓
Client / Utility
↓
Application6. Choose ONE Existing Test
Do not read 100 tests. Pick one small, representative test.
test('user can search for a product', async ({ homePage }) => {
await homePage.goto();
await homePage.search('Laptop');
await expect(homePage.searchResults).toContainText('Laptop');
});Now investigate everything involved in running that single test.
productSearch.spec.ts
↓
homePage fixture
↓
HomePage
↓
SearchComponent
↓
Locator
↓
Browser
↓
ApplicationThis dependency-tracing technique is one of the fastest ways to understand a large framework.
7. Understand the Fixture Architecture
Fixtures establish the environment and dependencies a test needs, and teams extend the built-in fixtures with their own. Suppose you find this test signature:
test('create order', async ({
authenticatedPage,
orderService,
dbClient
}) => {
});Ask where each of those dependencies comes from. You might discover:
import { test as base } from '@playwright/test';
export const test = base.extend({
authenticatedPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'playwright/.auth/user.json'
});
const page = await context.newPage();
await use(page);
await context.close();
},
orderService: async ({ request }, use) => {
const service = new OrderService(request);
await use(service);
}
});Now you understand that fixtures act like a dependency-injection system for the tests. This is why blindly importing test from @playwright/test can be wrong in an enterprise framework.
// Bypasses the framework
import { test } from '@playwright/test';
// Correct in this repository
import { test } from '../../fixtures/test.fixture';The customised test object may provide authentication, Page Objects, API clients, database clients, logging, test data, cloud utilities and application services. Importing the raw one silently bypasses part of the architecture.
8. Understand Authentication
Next investigate how login and session management work. Search the repository for these terms:
storageState
auth
login
authenticatedPage
globalSetup
setup projectplaywright/
└── .auth/
├── admin.json
├── manager.json
└── customer.jsonPlaywright supports saving authenticated browser state and reusing it instead of logging in through the UI every time. Saved state can contain sensitive cookies and headers, so it should never be committed to source control.
- How is login performed — UI, API, SSO or OAuth?
- Is a stored browser session reused?
- Are there multiple roles (Admin, Manager, Analyst, Customer, Read-only)?
- When does authentication expire?
- How are credentials retrieved, and how does CI authenticate?
9. Understand Environment Management
Find the environment plumbing:
.env
.env.example
config/
├── dev.ts
├── stage.ts
└── qa.tsconst environments = {
dev: {
baseURL: 'https://dev.example.com',
apiURL: 'https://api.dev.example.com'
},
stage: {
baseURL: 'https://stage.example.com',
apiURL: 'https://api.stage.example.com'
}
};- What is the default environment and how do you switch?
- Which environment runs in CI?
- Where do secrets come from?
- Are usernames, test data and API endpoints environment-specific?
If the framework already configures a baseURL, never hard-code a full host in a test.
// Wrong
await page.goto('https://stage.example.com/products');
// Right
await page.goto('/products');10. Understand Page Objects and Component Objects
Now inspect the UI abstraction layer.
export class LoginPage {
constructor(private page: Page) {}
username = this.page.getByLabel('Username');
password = this.page.getByLabel('Password');
loginButton = this.page.getByRole('button', { name: 'Login' });
async login(username: string, password: string) {
await this.username.fill(username);
await this.password.fill(password);
await this.loginButton.click();
}
}Some enterprise frameworks compose pages from smaller component objects:
ProductPage
│
├── HeaderComponent
├── SearchComponent
├── ProductTableComponent
└── PaginationComponentUnderstand the existing design philosophy before creating new classes.
11. Understand the Service Layer
More advanced frameworks add business-level services on top of technical clients.
export class OrderService {
constructor(
private orderApi: OrderApi,
private dbClient: DbClient
) {}
async createOrder(orderData: OrderData) {
const response = await this.orderApi.createOrder(orderData);
await this.dbClient.waitForOrder(response.id);
return response;
}
}The test then reduces to a single business call:
const order = await orderService.createOrder(orderData);Instead of every test constructing a payload, posting, parsing, querying the database and retrying, the flow becomes layered:
Test
↓
Business Service
↓
Technical Client
↓
Application12. Understand API Clients
Enterprise Playwright projects test far more than the UI.
clients/
├── restClient.ts
├── graphQLClient.ts
├── websocketClient.ts
└── messageClient.tsexport class RestClient {
constructor(private request: APIRequestContext) {}
async post(path: string, body: unknown) {
return this.request.post(path, { data: body });
}
}
export class ProductService {
constructor(private client: RestClient) {}
async createProduct(product: Product) {
return this.client.post('/api/products', product);
}
}Test
↓
ProductService
↓
RestClient
↓
POST /api/products
↓
BackendBefore calling request.post(...) directly from a new test, check whether the framework already provides a client or service for that endpoint.
13. Understand Test Data Management
Search for test-data, factories, builders, faker usage or fixture data folders.
export const createCustomer = () => ({
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email()
});const customer = CustomerBuilder
.create()
.withPremiumSubscription()
.withActiveStatus()
.build();Determine whether tests use static data, dynamic data, API-generated data, database-seeded data, shared accounts, factories, builders or environment-specific data.
Then investigate cleanup. If a test creates a customer, order, subscription or product, what removes it? Common approaches are API cleanup, database cleanup, afterEach or afterAll hooks, automatic expiration, or a dedicated disposable environment.
14. Understand Database Integration
Look for dbClient.ts, database fixtures, repositories, sql/ or queries/.
const order = await db.query(
`
SELECT *
FROM orders
WHERE order_id = $1
`,
[orderId]
);
// or a cleaner abstraction
const order = await orderRepository.findById(orderId);- Can automation read the database? Can it modify it?
- Is database access only for validation?
- Does the framework use repositories?
- How are connections managed and closed?
- How is data cleaned, and are queries environment-specific?
Do not scatter raw SQL through every test if the architecture already provides reusable database abstractions.
15. Understand Asynchronous Processing
Enterprise applications frequently use asynchronous workflows.
Playwright
↓
POST /orders
↓
Order Service
↓
Kafka
↓
Payment Service
↓
Database
↓
WebSocket
↓
UI updatedThe result may not appear immediately. Search the framework for waitFor, poll, retry, eventually, timeout or waitUntil helpers.
await expect.poll(
async () => await dbClient.getOrderStatus(orderId)
).toBe('COMPLETED');If such utilities exist, use them. Avoid arbitrary waits such as page.waitForTimeout(5000) — the real system may finish in 500ms or in 8 seconds depending on the environment.
16. Understand Reporting and Debugging
Determine how failures are diagnosed. Look for reporters, logging, attachments, allure or test-results directories.
Screenshot
Video
Playwright Trace
Browser Console
Network Requests
API Request
API Response
Database Result
Test Data
Allure Stepsawait testInfo.attach('API Response', {
body: JSON.stringify(response, null, 2),
contentType: 'application/json'
});Before creating your own logging system, learn what already exists.
17. Understand CI/CD Execution
Finally inspect .github/workflows/, Jenkinsfile, azure-pipelines.yml or Dockerfile. Local execution can be very different from CI execution.
- name: Run Regression
run: >
npx playwright test
--project=chromium
--grep @regression- What runs for a pull request, after deployment, nightly and during regression?
- Which environments are used and how are secrets supplied?
- How many workers run, and are tests sharded?
- Where are reports uploaded and what happens when tests fail?
The Most Important Exercise: Trace ONE Test End-to-End
This is the single most valuable exercise for a new SDET. Suppose you find:
test('customer can purchase product', async ({
productPage,
orderService,
dbClient
}) => {
const product = await orderService.createProduct();
await productPage.goto(product.id);
await productPage.buy();
await expect(productPage.successMessage).toBeVisible();
const order = await dbClient.getOrder(product.id);
expect(order.status).toBe('CREATED');
});Now trace every layer it touches:
purchase.spec.ts
│
▼
test.fixture.ts
│
├───────────────┐
▼ ▼
ProductPage.ts OrderService.ts
│
▼
OrderApi.ts
│
▼
RestClient.ts
│
▼
POST /orders
│
▼
Order Service
│
▼
Kafka
│
▼
Database
│
▼
DbClient.tsWhat to Do Before Writing Your First New Test
Once you understand the architecture you are ready for a real ticket — but do not start coding immediately.
Step 1 — Understand the Requirement
Read the user story, acceptance criteria, specification, business rules, API contract and design. Identify who, what, when, expected behaviour, preconditions, business rules, negative conditions and dependencies. Never automate something you do not understand.
Step 2 — Walk the Flow Manually
Login
↓
Search Product
↓
Open Product
↓
Add to Cart
↓
Checkout
↓
Create Order
↓
ConfirmationWhile doing this, inspect UI behaviour, network requests, API responses, required test data, permissions, error handling and backend dependencies.
Step 3 — Search for Existing Automation
Search the repository by feature name, page name, API endpoint, business terminology and ticket number. Do not write your own createProduct() if productService.createProduct() already exists.
Step 4 — Find the Closest Existing Test
If your ticket is Create Customer, study Update Customer, Delete Customer, Customer Search and Customer Subscription. Enterprise conventions matter more than personal coding preference.
Step 5 — Identify Required Components
Before coding, check what already exists: fixture, Page Object, component object, service, REST client, GraphQL client, database helper, test-data factory, schema, wait utility. Reuse before creating.
Step 6 — Determine the Test Data Strategy
Ask how your test will obtain data, whether the API can create it, whether a factory or fixture exists, and how it will be cleaned. Instead of spending 30 seconds creating a customer through the UI, prefer:
const customer = await customerService.createCustomer();Then use the UI only for the behaviour actually under test.
Step 7 — Run Existing Related Tests
npx playwright test tests/ui/products
npx playwright test --grep "@product"This confirms your environment, authentication, test data and framework setup all work before you change anything — otherwise you may spend hours debugging a pre-existing problem.
Step 8 — Write the Test Using Existing Patterns
Your new test should look like it belongs to the same framework: same fixtures, services, Page Objects, factories, tags, logging and reporting steps. Avoid introducing a completely different architecture for one test.
A Professional New-Test Workflow
Requirement
↓
Acceptance Criteria
↓
Application Walkthrough
↓
Search Existing Tests
↓
Find Closest Example
↓
Identify Existing Components
↓
Determine Test Data
↓
Run Existing Tests
↓
Implement Test
↓
Run Locally
↓
Debug with Trace/Logs
↓
Run Related Regression
↓
Code Review
↓
CIThe First Five Places I Would Inspect
1. README.md
↓
2. package.json
↓
3. playwright.config.ts
↓
4. Main fixture file
↓
5. One representative *.spec.tsThen follow that test's dependencies:
product.spec.ts
↓
fixture
↓
ProductPage
↓
ProductService
↓
RestClient
↓
Database UtilityThis is far more effective than randomly opening 100 files.
Questions You Should Be Able to Answer
Before saying “I understand this framework”, you should be able to explain:
- How does a test start?
- How is the environment selected?
- How does authentication work?
- How are fixtures injected?
- How are Page Objects created?
- How are APIs called?
- How is test data created?
- How is database validation performed?
- How are asynchronous workflows handled?
- How is data cleaned?
- How are failures debugged?
- How does CI execute the tests?
Common Mistakes When Joining an Existing Playwright Project
Mistake 1: Immediately writing new tests
Understand the framework first. The first pull request sets your reputation on the team.
Mistake 2: Copying tests without understanding dependencies
Copied code can duplicate existing architecture or silently bypass important fixtures.
Mistake 3: Creating new utilities too quickly
Search first — the utility very often already exists under a different name.
Mistake 4: Hard-coding environments
// Bad
await page.goto('https://qa.company.com/products');
// Better
await page.goto('/products');Mistake 5: Logging in through the UI in every test
The framework probably already provides reusable authenticated state per role.
Mistake 6: Using hard waits
Avoid page.waitForTimeout(5000). Understand the application's actual synchronisation mechanism instead.
Mistake 7: Ignoring test-data cleanup
Creating records without cleanup eventually destroys test reliability and pollutes shared environments.
Mistake 8: Running only the new test
Your change may affect fixtures, Page Objects or utilities used by many other tests — run the relevant regression scope too.
Mistake 9: Introducing your own architecture
Your preferred design may be excellent, but first understand why the existing team made its architectural decisions.
Enterprise Framework Onboarding Checklist
Before creating your first pull request, make sure you understand:
APPLICATION
☐ Major application workflows
☐ User roles
☐ Backend dependencies
☐ Critical business rules
FRAMEWORK
☐ README
☐ package.json
☐ playwright.config.ts
☐ Folder structure
☐ Test execution lifecycle
PLAYWRIGHT
☐ Fixtures
☐ Page Objects
☐ Components
☐ Authentication
☐ Projects
☐ Retry/timeout strategy
BACKEND
☐ REST clients
☐ GraphQL clients
☐ Database utilities
☐ Messaging/WebSocket utilities
DATA
☐ Test-data strategy
☐ Factories/builders
☐ Environment-specific data
☐ Cleanup strategy
OBSERVABILITY
☐ Logging
☐ Screenshots
☐ Video
☐ Trace
☐ Reports
DEVOPS
☐ Git workflow
☐ CI/CD
☐ Tags
☐ Smoke tests
☐ Regression tests
☐ Scheduled testsRecommended First-Day Investigation
Repository
│
├── README
│
├── package.json
│
└── playwright.config
│
▼
Folder Structure
│
▼
Fixtures
│
▼
Authentication
│
▼
Simple Test
│
▼
Page Object
│
▼
Service
│
▼
API Client
│
▼
Test Data
│
▼
Database
│
▼
Reporting
│
▼
CI/CDYou do not need to understand every function in the framework. You need to understand the architecture and the execution flow.
Think Like a Test Architect
A junior automation engineer asks “where is the test file?”. A senior SDET asks “how does this test execute through the framework?”. A Test Architect asks why the framework was designed this way, what problem each layer solves, and where new functionality belongs.
Business Requirement
↓
Test Scenario
↓
Fixture
↓
Page / Component / Service
↓
REST / GraphQL / Messaging
↓
Application
↓
Database
↓
Validation
↓
Reporting
↓
CI/CDFinal Takeaway
When joining a company with an existing enterprise Playwright framework, do not begin by writing code. Begin by building a mental model of the system.
Understand Application
↓
Read README
↓
Inspect package.json
↓
Inspect playwright.config.ts
↓
Understand Folder Structure
↓
Understand Fixtures
↓
Understand Authentication
↓
Trace One Existing Test
↓
Understand Page/Service/API Layers
↓
Understand Test Data
↓
Understand DB/Messaging
↓
Understand Reporting
↓
Understand CI/CD
↓
Run Existing Tests
↓
Write New TestThe goal is not simply to make another Playwright test pass. The goal is to add a test that fits the existing enterprise automation architecture and stays maintainable for the entire engineering team.
Complete professional Playwright + TypeScript framework covering UI, REST API, GraphQL, WebSocket, Kafka, Database, E2E and Agentic AI testing — with a complete AUT included.
Get Playwright tutorials in your inbox
Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.
Playwright Framework Series
View all →- 1Getting Started with Playwright: Installation, Setup, and Your First Test
- 2Playwright Locators: The Complete Guide with Real-World Examples
- 3Playwright Actions: Complete Guide to Click, Fill, Hover, Keyboard, Mouse & File Upload
- 4Playwright Assertions: The Complete Guide with Real-World Examples
- 5Playwright Auto Waiting: The Complete Guide with Real-World Examples
- 6Playwright Fixtures: The Complete Guide with Real-World Examples
- 7Playwright Browser Context & Multiple Tabs: The Complete Guide with Real-World Examples
- 8Playwright Authentication & Session Management: Complete Guide with Enterprise Examples
- 9Playwright Network Interception & API Mocking: Complete Guide with Real-World Examples
- 10Playwright Page Object Model (POM): The Complete Guide with Enterprise Examples
- 11Page Object Model with Playwright: A Practical Guide
- 12How to Build a Robust Professional Playwright Framework from Scratch (Step-by-Step)
- 13Building an Enterprise Playwright Framework from Scratch
API Testing Series
View all →- 1Playwright API Testing: The Complete Guide with Real-World Examples
- 2Part 1 : Playwright API Testing Tutorial: Build Enterprise-Level API Automation Framework
- 3Part 2: Playwright API Testing Tutorial: Setting Up Project & Sending Your First API Request
- 4Part 3: Mastering CRUD Operations in Playwright API Testing
- 5Part 4: Authentication in Playwright API Testing (Bearer Token, JWT, API Key & Reusable Fixtures)
- 6Part 5: Building an Enterprise-Level Playwright API Automation Framework
- 7Part 6: API Models, Schema Validation & Test Data Management in Playwright
- 8Part 7: Custom Fixtures, Hooks, Logging & Parallel Execution in Playwright API Testing
- 9Part 8: Building a Production-Ready Playwright API Framework (Environment Management, CI/CD, Reporting & Best Practices)
- 10Part 9: Advanced Playwright API Testing Techniques for Enterprise Automation
- 11Part 10: Building a Complete Enterprise Playwright API Automation Framework (Final Part)
- 12API Testing with Playwright: Request Context, Auth, and Assertions
- 13Playwright Backend Testing Tutorial – REST API, GraphQL, WebSocket, Kafka, Database & Microservices