Skip to content

Playwright Project Anatomy — Every File & Folder Explained

Who this is for: Someone opening a Playwright project for the first time and wondering — what are all these files? Why is node_modules huge? What do package.json, playwright.config.ts, and tsconfig.json actually do? This page explains the architecture of the project files — not how to write tests.


1. The Big Picture — What You See When You Open the Folder

my-playwright-project/ ├── node_modules/ ← installed libraries (HUGE — never edit, never commit) ├── tests/ ← your test files live here │ └── example.spec.ts ├── tests-examples/ ← demo tests Playwright generates (safe to delete) ├── playwright-report/ ← HTML report from the last run (generated) ├── test-results/ ← traces, screenshots, videos from runs (generated) ├── package.json ← project identity card + dependency list ├── package-lock.json ← exact versions lockfile (auto-generated) ├── playwright.config.ts ← Playwright's control panel ├── tsconfig.json ← TypeScript compiler settings └── .gitignore ← tells Git which files NOT to track

The golden rule: files split into three categories —

Category Files Do you edit them?
You write tests/, playwright.config.ts, tsconfig.json, package.json ✅ Yes
Tool-generated, committed package-lock.json ❌ Never by hand — npm manages it
Tool-generated, NOT committed node_modules/, playwright-report/, test-results/ ❌ Never — rebuildable anytime

2. package.json — The Project's Identity Card

This is the single most important file in any Node.js project. Every JavaScript/TypeScript project — Playwright or not — has one. It declares what the project is and what it needs.

json { "name": "my-playwright-project", "version": "1.0.0", "description": "E2E tests for the checkout flow", "scripts": { "test": "playwright test", "test:headed": "playwright test --headed", "report": "playwright show-report" }, "devDependencies": { "@playwright/test": "^1.49.0", "@types/node": "^22.0.0", "typescript": "^5.6.0" } }

What each section means

Section Purpose
name / version / description Project metadata — identifies it if ever published or shared
scripts Command shortcuts. npm run test executes playwright test. Teams put their standard commands here so everyone runs tests the same way
dependencies Libraries the application needs at runtime (a pure test project often has none)
devDependencies Libraries needed only for development/testing — Playwright itself lives here, because the test framework is never shipped to production

The ^ in version numbers

"@playwright/test": "^1.49.0" — the caret ^ means "any version from 1.49.0 up to (but not including) 2.0.0". It allows minor updates automatically. This flexibility is exactly why the lockfile (next section) exists.

Mental model: package.json is the shopping list — "I need Playwright, roughly version 1.49".


3. package-lock.json — The Exact-Versions Receipt

When you run npm install, npm reads the shopping list (package.json), resolves every library and every library those libraries need (the full dependency tree), and writes the exact version of everything into package-lock.json.

json { "packages": { "node_modules/@playwright/test": { "version": "1.49.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", "integrity": "sha512-Wo1bWz..." } } }

Why it exists — the reproducibility problem

Without a lockfile: you install in January and get Playwright 1.49.0; your teammate installs in March and gets 1.49.5; CI installs in June and gets 1.50.1. Three machines, three different versions, "works on my machine" bugs.

With the lockfile: everyone gets byte-for-byte identical installs. The integrity hash even verifies the downloaded file wasn't tampered with (supply-chain security).

package.json package-lock.json
Written by You npm automatically
Contains Direct dependencies, version ranges The entire tree, exact versions + hashes
Size ~30 lines Often 1,000s of lines
Edit by hand? Yes Never
Commit to Git? Yes Yes — always (this is what makes CI reproducible)

Mental model: package-lock.json is the itemised receipt — exactly what was bought, down to the batch number.


4. node_modules/ — The Installed Libraries

This folder is where npm install physically downloads every library from the lockfile. It is enormous (often 100–500 MB and tens of thousands of files) because it contains not just Playwright, but everything Playwright depends on, and everything those depend on — the complete tree.

The three rules of node_modules

  1. Never edit anything inside it. Changes are wiped on the next install.
  2. Never commit it to Git. It's listed in .gitignore. It is 100% rebuildable from package-lock.json by running npm install.
  3. If it's ever corrupted or weird — delete it and reinstall. rm -rf node_modules && npm install is the classic fix.

Why is it SO big?

Each package ships its own code, type definitions, licences, and sometimes multiple builds (CommonJS + ES Modules). And npm installs the transitive closure — your 3 declared dependencies can expand to 300+ actual packages.

flowchart TB
    PJ["📄 package.json<br/>(your shopping list — 3 packages)"] -->|npm install| NM["📦 node_modules/<br/>(full tree — 300+ packages)"]
    PJ -->|npm resolves + records| PL["📄 package-lock.json<br/>(exact receipt)"]
    PL -->|guarantees identical| NM
    NM -.->|never committed| GIT["🚫 .gitignore"]

Note: Playwright's browsers (Chromium, Firefox, WebKit) are NOT in node_modules — they're downloaded separately by npx playwright install into a shared cache (%USERPROFILE%\AppData\Local\ms-playwright on Windows). That's why a fresh clone needs both npm install and npx playwright install.


5. playwright.config.ts — Playwright's Control Panel

This is Playwright's own settings file — it controls how tests run, not what they test. When you run npx playwright test, the very first thing Playwright does is read this file.

```ts import { defineConfig, devices } from '@playwright/test';

export default defineConfig({ testDir: './tests', // where to find test files timeout: 30_000, // max time per test (ms) retries: process.env.CI ? 2 : 0, // retry flaky tests in CI only workers: 4, // run 4 tests in parallel reporter: [['html'], ['list']], // how results are presented

use: { // defaults applied to EVERY test baseURL: 'https://staging.myapp.com', trace: 'on-first-retry', // record a trace when a retry happens screenshot: 'only-on-failure', video: 'retain-on-failure', },

projects: [ // run the same tests on multiple browsers { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, ], }); ```

The settings you'll touch most

Setting What it controls Why it matters for QA
testDir Folder containing test files Keeps tests separate from app code
timeout Max duration per test Prevents hung tests blocking the pipeline
retries Auto-retry count for failures Flaky-test management — typically CI-only
workers Parallelism 4 workers ≈ 4× faster suite
baseURL Root URL — tests use page.goto('/login') Switch environments (dev/staging/prod) in ONE place
trace / screenshot / video Failure evidence capture Debugging CI failures without re-running
projects Browser/device matrix One test suite → Chrome + Firefox + Safari + mobile
reporter Output format (html, list, junit, json) junit feeds CI dashboards; html for humans

Why .ts and not .json?

Because it's code, not just data — note the line retries: process.env.CI ? 2 : 0. That's logic: "2 retries on CI, 0 locally". A JSON file can't express conditions; a TypeScript file can. This is a common pattern in modern JS tooling.

Mental model: playwright.config.ts is the cockpit — every dial that changes how the test run behaves lives here, so the tests themselves stay clean.


6. tsconfig.json — The TypeScript Compiler's Rulebook

Your tests are written in TypeScript (.ts) — JavaScript plus type checking. But browsers and Node.js only execute JavaScript. tsconfig.json tells the TypeScript compiler how to interpret and check your .ts files.

json { "compilerOptions": { "target": "ESNext", // which JavaScript version to produce "module": "commonjs", // how import/export is wired "moduleResolution": "node", // how 'import x from y' finds y "strict": true, // maximum type-safety checks "esModuleInterop": true, // smooths over import style differences "baseUrl": ".", "paths": { "@pages/*": ["pages/*"], // import shortcuts: '@pages/login' instead of '../../pages/login' "@utils/*": ["utils/*"] } }, "include": ["tests/**/*.ts", "pages/**/*.ts"] }

What each key setting does

Setting Plain-English meaning
target Which generation of JavaScript to output (ESNext = latest)
module The import/export system to use — Node traditionally uses commonjs
strict Turns on ALL type-safety checks — catches undefined bugs before runtime. Always keep on
paths Import aliases — @pages/login instead of fragile ../../../pages/login relative paths
include Which folders TypeScript should check

Why TypeScript for tests at all?

Because your editor catches errors as you type, before any test runs:

ts await page.goto(loginUrl); // ✅ TypeScript knows page.goto exists and takes a string await page.gotoo(loginUrl); // ❌ red underline INSTANTLY: 'gotoo' does not exist await expect(page).toHaveTitle(42); // ❌ caught: expected string/RegExp, got number

In a 500-test suite, that's hundreds of typo-class failures eliminated at write time instead of discovered at run time (10 minutes into a CI pipeline).

Subtle point: Playwright actually runs your .ts test files directly (it transpiles them on the fly) — so tsconfig.json here mostly powers editor intelligence and type-checking, not a separate build step. That's why a Playwright project has no dist/ build output folder.


7. The Generated Folders — Evidence & Reports

Folder What's inside Lifecycle
playwright-report/ Self-contained HTML report of the last run — open with npx playwright show-report Overwritten every run · gitignored
test-results/ Raw evidence per failed test: traces (full DOM/network/console timeline), screenshots, videos Overwritten every run · gitignored
tests-examples/ Demo tests scaffolded by npm init playwright Delete once you've read them
.cache/ (sometimes) Transpiled test code cache Ignorable

The trace viewer deserves a special mention: a .zip trace in test-results/ opened with npx playwright show-trace gives you a time-travel debugger — every action, DOM snapshot, network call, and console log of the failed test. In CI, traces are the difference between "it failed, re-run it" and "I can see exactly why it failed".


8. How It All Connects — One Diagram

flowchart TB
    DEV["👩‍💻 You run: npx playwright test"] --> CFG["⚙️ playwright.config.ts<br/>read first — testDir, browsers,<br/>workers, retries, baseURL"]
    CFG --> TS["📘 tsconfig.json<br/>governs how your .ts files<br/>are type-checked & resolved"]
    TS --> TESTS["🧪 tests/*.spec.ts<br/>your actual test code"]
    TESTS --> RT["📦 node_modules/@playwright/test<br/>the framework engine<br/>(installed per package-lock.json)"]
    RT --> BR["🌐 Browsers<br/>Chromium · Firefox · WebKit<br/>(separate cache, via npx playwright install)"]
    BR --> OUT["📊 Outputs<br/>playwright-report/ (HTML)<br/>test-results/ (traces, screenshots)"]

The chain in words: the command reads the config, which locates your tests, which are type-checked per tsconfig, executed by the framework in node_modules (whose exact version is pinned by package-lock.json, which was declared in package.json), driving real browsers, producing reports and traces.


9. Fresh-Clone Checklist — What Happens on a New Machine

When you clone a Playwright repo, only the committed files arrive. Everything generated is rebuilt:

bash git clone <repo> # you get: tests/, package.json, package-lock.json, configs cd <repo> npm install # rebuilds node_modules/ EXACTLY per package-lock.json npx playwright install # downloads the browser binaries (separate cache) npx playwright test # reads playwright.config.ts and runs everything npx playwright show-report # opens the HTML report

Step Reads Creates
npm install package-lock.json node_modules/
npx playwright install browser cache (outside the project)
npx playwright test playwright.config.ts, tsconfig.json, tests/ playwright-report/, test-results/

10. Quick Reference Card

File / Folder One-liner Commit? Edit?
package.json Project identity + dependency shopping list + npm scripts
package-lock.json Exact dependency receipt — guarantees identical installs ❌ (npm manages)
node_modules/ The installed libraries themselves
playwright.config.ts How tests run: browsers, parallelism, retries, URLs, evidence
tsconfig.json TypeScript checking rules + import aliases
tests/ Your test code
playwright-report/ HTML report of last run
test-results/ Traces / screenshots / videos of failures
.gitignore The list keeping the ❌ rows out of Git

11. Where to Go Next