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_moduleshuge? What dopackage.json,playwright.config.ts, andtsconfig.jsonactually 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.jsonis 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.jsonis 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¶
- Never edit anything inside it. Changes are wiped on the next install.
- Never commit it to Git. It's listed in
.gitignore. It is 100% rebuildable frompackage-lock.jsonby runningnpm install. - If it's ever corrupted or weird — delete it and reinstall.
rm -rf node_modules && npm installis 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 bynpx playwright installinto a shared cache (%USERPROFILE%\AppData\Local\ms-playwrighton Windows). That's why a fresh clone needs bothnpm installandnpx 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.tsis 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
.tstest files directly (it transpiles them on the fly) — sotsconfig.jsonhere mostly powers editor intelligence and type-checking, not a separate build step. That's why a Playwright project has nodist/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¶
- AI Fundamentals for Beginners — if you're also new to the AI side
- QA → AI QA — 6-Week Transition — Playwright fits into week 1–2 of the plan
- Test Cases Generator AI Agent — AI-generated Playwright tests
- Official docs: playwright.dev — the
Getting StartedandTest Configurationpages expand on everything here