Node.js runs the test code, npm manages downloaded packages, and package.json records the project’s packages and commands.
Think of Node.js as a kitchen, npm as its supply service, and package.json as the ingredient list.
package.json → npm installs → Node.js runs → Playwright controls browser
Series: Playwright Zero to Expert | Demo app used throughout this series: saucedemo.com
There’s a reasonable question sitting underneath this whole part: JavaScript, as most people first encounter it, runs inside a browser — a webpage’s <script> tags execute JavaScript to make buttons interactive, animate things, and so on. So how can Playwright, a tool you run from your own computer’s command line, also be written in JavaScript? Browsers aren’t command-line tools. This part answers exactly that, and along the way gives you the two pieces of tooling — Node.js and npm — that everything from here on in this series depends on.
What Node.js Actually Is
For a long time, JavaScript’s only home was inside a browser — it had no way to run anywhere else. Node.js changed that. Node.js is a runtime that lets JavaScript execute outside of a browser entirely — directly on your computer, as an ordinary program, the same way Python or Java scripts run.
Think about what this actually unlocks: a script that reads files from your hard drive, or talks directly to a database, or — the part that matters most to you — launches and controls an actual browser from the outside, sending it commands and reading its state.
None of that is something a webpage’s JavaScript, running inside a browser tab, is normally allowed to do (for good reason — a random website shouldn’t be able to read your hard drive). Node.js gives JavaScript an entirely different environment to run in, with entirely different capabilities, suited to exactly this kind of task.
This is the direct, concrete reason Playwright needs Node.js: when you run a Playwright test, it’s not the browser’s own JavaScript engine executing your test code at all. It’s Node.js, running on your machine, executing your test file — and your test file, in turn, uses Playwright’s library to reach out, launch a real browser as a separate, controlled process, and drive it from the outside. Node.js is the engine your test code actually runs on; the browser is a separate thing being remotely controlled by it.
Your test file (test.spec.ts)
│
│ executed by
▼
Node.js ───────────────► launches & controls ───────────────► Real Browser
(running on your machine) (Chromium/Firefox/WebKit)
Installing Node.js
Before any Playwright project can exist, Node.js needs to be installed on your machine. The process differs slightly by operating system, but the underlying idea is the same everywhere: download an installer from the official source, run it, and verify it worked.
- Windows / macOS — go to
nodejs.org, download the installer for the LTS (Long-Term Support) version (this is deliberately the recommended, stable choice over the newer “Current” version — LTS releases get longer-term stability and security support, which matters for a tool you’ll depend on for real project work), and run it like any other installer. - macOS (alternative) — if you use Homebrew,
brew install nodeworks just as well. - Linux — typically through your distribution’s package manager, or via a version manager like
nvm(Node Version Manager), which also lets you switch between multiple installed Node versions easily — genuinely useful once you’re working across different projects that might expect different Node versions.
Once installed, verify it actually worked by opening a terminal (Command Prompt or PowerShell on Windows, Terminal on macOS/Linux) and running:
node --version
v20.11.0
That output confirms Node.js is installed and tells you exactly which version — worth noting down, because occasionally a project (or this series’ examples) might expect a minimum version, and this is how you check what you’re actually running.
npm — the Package Manager
Installing Node.js also installs something else alongside it, automatically: npm (Node Package Manager). Verify it the same way:
npm --version
10.2.4
Here’s the problem npm exists to solve. Playwright itself — the actual code that knows how to launch and control browsers — is not something you write yourself. It’s a package: a chunk of pre-written, reusable code that someone else (in this case, Microsoft, who maintains Playwright) has published for anyone to use.
Without a package manager, using someone else’s code would mean manually downloading files, figuring out where to put them, and manually tracking which version you have — for Playwright, and separately for every other package your project might need. npm automates this entire process: it downloads packages, tracks exactly which versions you’re using, and manages the (sometimes deep) chain of other packages those packages themselves depend on.
A dependency is simply any package your project needs in order to work — Playwright is a dependency of your test project.
package.json
Every Node.js project — including every Playwright project you’ll build in this series — has a file at its root called package.json. Think of it as your project’s identity card and dependency manifest, all in one:
{
"name": "playwright-saucedemo-tests",
"version": "1.0.0",
"scripts": {
"test": "playwright test"
},
"devDependencies": {
"@playwright/test": "^1.45.0"
}
}
nameandversion— basic identifying information about your own project.devDependencies— packages needed only for development and testing, not for running the actual application in production. This is exactly where Playwright lives, since it’s a testing tool, not something your application needs to function for real users. (You’ll also see a plaindependenciesfield in other kinds of projects, for packages an application genuinely needs at runtime — but for a test project specifically,devDependenciesis where almost everything you install will land.)^1.45.0— that caret (^) matters. It means “this version, or any newer compatible version” — npm is allowed to install version1.46.0or1.50.2automatically, but not2.0.0, because a jump to a new major version number typically signals breaking changes. This single character is a small but real example of how npm tries to balance “keep things reasonably up to date” against “don’t silently break my project.”scripts— shortcuts for commands you’ll run often. Here, typingnpm run test(or justnpm test) actually runsplaywright testbehind the scenes, saving you from typing the longer command every time. You’ll define several of these as your Playwright projects grow — running tests in different browsers, generating reports, and so on.
package-lock.json
Alongside package.json, npm also generates (and constantly updates) a file called package-lock.json. Here’s the exact problem it solves: package.json says Playwright should be “^1.45.0 or compatible” — but that’s a range, not one single, exact version.
If two different people, or two different machines (say, your laptop and your CI server), each run npm install on different days, that flexible range could quiet, without anyone intending it, resolve to two genuinely different actual versions of Playwright — which can lead to the maddening situation where “it works on my machine” but fails somewhere else, for reasons that have nothing to do with your actual test code.
Analogy: The Grocery List vs. The Factory Invoice Imagine buying items for a recipe:
package.json(The Grocery List): You write down: “Buy peanut butter, white bread, and strawberry jam.” If you and your teammate go to two different supermarkets, one of you might buy organic peanut butter (16oz jar) and the other might buy chunky store-brand peanut butter (24oz jar). The recipes will taste slightly different.package-lock.json(The Factory Invoice): This lists: “Buy Jif Creamy Peanut Butter (Barcode 1234567, Lot Number 890), Wonder Bread White (Barcode 456789).” No matter who goes to the store, or on what day, they are guaranteed to purchase the exact same ingredients from the exact same manufacturer down to the batch number.
📊 Visual Flowchart: The Node.js and npm Project Pipeline
Here is how dependency manifests interact with npm commands to fetch code and run tests locally:
graph TD
Dev["Developer runs npm install"] --> ReadPackage["Read package.json<br>(Reads dependencies range)"]
ReadPackage --> CheckLock{"Does package-lock.json exist?"}
CheckLock -->|Yes| LockIn["Fetch exact versions from package-lock.json"]
CheckLock -->|No| Registry["Query npm Registry for latest version matching range"]
Registry --> WriteLock["Create/Update package-lock.json"]
LockIn --> Download["Download packages code"]
WriteLock --> Download
Download --> NodeModules["Save into node_modules/ folder"]
RunTest["Developer runs npx playwright test"] --> FindLocal["npx searches node_modules/"]
FindLocal --> RunCode["Node.js runs local binary on machine"]
package-lock.json locks in the exact, specific version of every single package (and every dependency of every dependency) that was actually installed, the moment it was installed. As long as this file is committed to version control (Part 5 covers Git properly) and everyone on the team uses it, npm install will install the exact same versions, every time, on every machine — eliminating an entire category of “works for me, not for you” bugs before they can even happen.
node_modules
When you run npm install, all the actual downloaded package code — Playwright itself, and everything Playwright itself depends on internally — gets placed into a folder called node_modules. This folder can get genuinely large (often hundreds of megabytes, sometimes more, for a real project with several dependencies), and it’s never something you’re expected to open, read, or manually edit.
Because node_modules can always be regenerated at any time simply by running npm install again (as long as package.json and package-lock.json exist), it’s standard practice to never commit it to Git — we’ll set up a .gitignore file explicitly excluding it in Part 5, and it’s worth understanding why now: committing hundreds of megabytes of code that can be perfectly regenerated from two small files is wasted space and unnecessary noise in your project’s history.
Global vs. Local Packages, and npx
npm can install a package in two different ways, and the distinction genuinely matters:
- Local install (
npm install @playwright/test) — installs the package inside this specific project’snode_modulesfolder, usable only within this project. This is, deliberately, how you’ll install Playwright itself — it means different projects on your machine can each use their own version of Playwright without conflicting with each other. - Global install (
npm install -g some-package) — installs a package once, available from anywhere on your machine, from any project or folder.
This raises a real, practical question: if Playwright is installed locally, how do you run its command-line tool without a global install? This is exactly the problem npx solves. npx temporarily runs a locally-installed package’s command-line tool, without needing it installed globally at all:
npx playwright test
This command finds Playwright inside your project’s own local node_modules, and runs it — even though Playwright was never installed globally on your system. This matters more than it might seem: it means every project on your machine can depend on its own exact version of Playwright (locked in by package-lock.json, as covered above), without version conflicts between projects, while npx transparently handles finding and running the right one for whichever project you’re currently in.
Bringing It Together — What Actually Happens When You Set Up a Playwright Project
We’ll do this hands-on properly in Part 6, but it’s worth previewing the sequence now, so none of it feels unfamiliar when we get there:
1. Install Node.js on your machine (one-time setup)
↓
2. Create a project folder
↓
3. Run "npm init playwright@latest" — this creates package.json,
installs Playwright as a devDependency, and generates starter files
↓
4. package-lock.json is generated, locking exact versions
↓
5. node_modules is populated with Playwright's actual code
↓
6. Run "npx playwright test" to execute your tests
— npx finds Playwright locally, Node.js runs it,
and Playwright launches and controls a real browser
Every piece of vocabulary in that sequence — Node.js, npm, package.json, package-lock.json, node_modules, npx — is now something you understand the purpose of, not just a name you’re expected to memorize and trust blindly.
How It Works in a Real Test Run
Node.js runs the Playwright test runner. npm reads package.json to understand dependencies and scripts, package-lock.json pins the resolved dependency tree, and npm ci recreates that exact tree in CI before Playwright installs or locates compatible browser binaries.
Keeping these layers distinct makes setup failures easier to diagnose: a Node problem, package-install problem, missing browser executable, and failing test are four different failure stages.
Interview Questions
Q: What is Node.js, and why does Playwright specifically need it?
Ans: Node.js is a runtime that lets JavaScript execute outside of a browser, directly on a machine as an ordinary program. Playwright needs it because your test code isn’t executed by a browser’s own JavaScript engine — it’s run by Node.js on your machine, and that Node.js process is what launches and remotely controls a separate, real browser from the outside. Node.js is the engine your test code runs on; the browser is a separate process being driven by it.
Q: What is npm, and what problem does it solve?
Ans: npm is Node.js’s package manager. It solves the problem of using code someone else has already written and published — like Playwright itself — without manually downloading files, tracking versions by hand, or resolving the chain of other packages that package itself depends on. It automates installing, updating, and tracking exactly which versions of which packages a project depends on.
Q: What is the difference between package.json and package-lock.json?
Ans: package.json describes a project’s dependencies using flexible version ranges (like ^1.45.0, meaning “this version or a newer compatible one”), along with other project metadata and scripts. package-lock.json records the exact, specific version of every package — and every dependency of every dependency — that was actually installed at a given point in time, ensuring that everyone who installs the project gets identical versions, rather than potentially different ones resolved from the same flexible range on different days or machines.
Q: Why is node_modules typically excluded from version control (Git)?
Ans: Because it can always be regenerated exactly, on demand, simply by running npm install, as long as package.json and package-lock.json are present. Committing it would mean storing potentially hundreds of megabytes of code that adds no real value to the project’s history, since it’s entirely derived from files that are already tracked.
Q: What does npx actually do, and why is it needed if Playwright is installed locally rather than globally?
Ans: npx runs a package’s command-line tool by finding it in the current project’s local node_modules, without requiring that package to be installed globally on the machine. This matters because installing Playwright locally, per project, means different projects can each depend on their own exact, independently-locked version of Playwright without conflicting with each other — and npx is what lets you conveniently run each project’s correct local version without needing a separate global install.
Q: Two developers run npm install on the same project, on different days, and end up with two different versions of a package, causing inconsistent test behavior between their machines. What likely went wrong, and how should the team prevent it?
Ans: This typically happens when package-lock.json either doesn’t exist yet or wasn’t committed to version control — without it, npm install resolves the flexible version ranges in package.json independently each time, which can genuinely produce different results on different days as new compatible versions get published. The fix is straightforward: commit package-lock.json to Git and make sure everyone on the team, and CI, installs from it, which guarantees identical, exact versions across every machine.
Exercises — Part 4
Understand: Without looking anything up, explain in your own words why Playwright, a JavaScript-based tool, needs something like Node.js at all, given that JavaScript already runs inside every browser.
Simple Practice:
Open a terminal and run node --version and npm --version. Write down both version numbers. If Node.js isn’t installed yet, install the LTS version from nodejs.org first, then check again.
Real-World Scenario:
A teammate says: “I don’t understand why we need package-lock.json — package.json already lists all our dependencies.” Write a short explanation, in your own words, of what could actually go wrong on their machine without it, using a concrete example involving two developers installing the same project on different days.
Challenge:
Explain, step by step, in your own words and without copying the diagram from this part, what you believe happens between running npm init playwright@latest in an empty folder and successfully running your very first test with npx playwright test. You’ll get to verify exactly how accurate your mental model was in Part 6.
Next: Part 5 — Git and GitHub
— the version control system that lets you (and a whole team) safely track, share, and collaborate on your test code over time.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed