Install Node.js: Checking Button Behavior on a Server Without a Browser

To run JavaScript on a server, you first need to install Node.js. I installed Node.js 24.21.0 on a WordPress server running Nginx and Apache, and used jsdom 30.1.3 to check a web page’s button script without a browser.
The nodejs in the default repository is 16.20.2, but jsdom 30.1.3, which I use to check scripts, requires Node.js 22.22.2 or later. So I installed it from dnf module stream 24.
1️⃣ Why install Node.js and jsdom on a server
Checking button scripts on a server takes two things: Node.js and jsdom.
🔲 Node.js
JavaScript usually runs inside a browser, but once you install Node.js, the server can read and run JavaScript too.
Installing Node.js means putting the JavaScript runtime that used to live only in the browser onto the server as well. That is why Node.js is used to build web servers, to create commands that process many files at once, or to load packages such as jsdom and check pages. Ghost is also a CMS that runs on Node.js.
🔲 jsdom
jsdom is a package for Node.js that reads HTML and turns it into a document whose buttons can be pressed, just like in a browser. It builds the document structure (DOM) that a browser creates when it opens a page, but inside the server and without a screen, so scripts find buttons and change text as if they were in a real browser. Because no window opens, it runs with a single command on the server.
In other words, the checks a person used to do by opening a browser and clicking things one by one are handled by the server with a command. That is why jsdom is also used to test button behavior automatically after every fix, to pull out only content such as titles and links from a page, or to edit post HTML in bulk.
If you fetch a page with PHP or curl, you only see the HTML that was first sent. Text that changes after a button is pressed only appears once the script runs.
A browser goes through four steps: getting the page, reading the page, pressing the button and showing it on screen. jsdom runs every step except the last one on the server, so a button’s position and size are checked in a browser.
2️⃣ Before installing: the default repository and module streams
Before running [dnf install nodejs], first check which version would be installed. The nodejs in the AlmaLinux 9.8 default repository is 16.20.2.
dnf list --available nodejsAvailable Packages
nodejs.x86_64 1:16.20.2-8.el9_4 appstreamThe module repository has four streams: 18, 20, 22 and 24. As of the check date, the 22 stream was at 22.23.2 and the 24 stream at 24.21.0.
dnf module list nodejsnodejs 18 common [d], development, minimal, s2i Javascript runtime
nodejs 20 common [d], development, minimal, s2i Javascript runtime
nodejs 22 common [d], development, minimal, s2i Javascript runtime
nodejs 24 common [d], development, minimal, s2i Javascript runtimeAccording to the npm registry, jsdom 30.1.3 requires 22.22.2 or later in the 22 line, 24.15.0 or later in the 24 line, or 26 and above. Both the 22 and 24 streams meet this requirement.
3️⃣ Install Node.js: dnf module
This time I installed the 24 stream, and before running it I first checked the list of packages to be installed with the [--assumeno] option.
dnf module install nodejs:24 --assumenoThe list contained only five packages: nodejs, npm, nodejs-libs, nodejs-docs and nodejs-full-i18n. No web server, PHP or kernel packages were included. After checking, run the installation with the [-y] option.
dnf module install -y nodejs:24Dependencies resolved.
==============================================================================================
Package Architecture Version Repository Size
==============================================================================================
Installing group/module packages:
nodejs x86_64 1:24.21.0-1.module_el9.8.0+321+1555b6a2 appstream 70 k
npm noarch 1:11.19.0-1.24.21.0.1.module_el9.8.0+321+1555b6a2 appstream 2.1 M
Installing dependencies:
nodejs-libs x86_64 1:24.21.0-1.module_el9.8.0+321+1555b6a2 appstream 24 M
Installing weak dependencies:
nodejs-docs noarch 1:24.21.0-1.module_el9.8.0+321+1555b6a2 appstream 5.3 M
nodejs-full-i18n x86_64 1:24.21.0-1.module_el9.8.0+321+1555b6a2 appstream 8.9 M
Installing module profiles:
nodejs/common
Enabling module streams:
nodejs 24
Transaction Summary
==============================================================================================
Install 5 Packages
Total download size: 40 M
Installed size: 185 M[Installing weak dependencies] lists the weak dependencies, packages that are good to install alongside. The download size is 40M and the installed size is 185M.
node -vv24.21.0npm -v11.19.0cat /etc/dnf/modules.d/nodejs.module[nodejs]
name=nodejs
stream=24
profiles=common
state=enablednode was installed as 24.21.0 and npm as 11.19.0. The only configuration file changed by the installation is [/etc/dnf/modules.d/nodejs.module], which records the 24 stream and the common profile as enabled.
4️⃣ Installing jsdom and checking a button script
jsdom is installed with npm into a working folder, and the working folder is created outside the web root.
mkdir ~/jsdom-test && cd ~/jsdom-test
npm init -y[npm init -y] creates package.json with default values, and jsdom is then installed into the same folder.
npm install jsdomadded 36 packages, and audited 37 packages in 2s
8 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilitiesnpm ls jsdomjsdom-test@1.0.0 /root/jsdom-test
└── jsdom@30.1.336 packages including jsdom 30.1.3 were installed, with 0 known vulnerabilities. The node_modules folder in the working folder is 26M.
node -e 'const { JSDOM } = require(`jsdom`); console.log(new JSDOM(`<h1>Hello</h1>`).window.document.querySelector(`h1`).textContent)'HelloIf jsdom reads the HTML made of a single h1 tag as a document and prints the text inside it, [Hello], the check is done.
Checking a button script means confirming on the server that the text changes correctly after a click. As an example, I created an HTML file in the working folder where clicking a [Copy] button changes its text to [Copied].
cat btn.html<button id="copy">Copy</button>
<script>
document.getElementById('copy').addEventListener('click', function () {
this.textContent = 'Copied';
});
</script>The check script reads the HTML file with jsdom, runs its scripts and prints the text before and after the click. Script errors are printed on lines starting with [Error:].
cat check.jsconst fs = require('fs');
const { JSDOM, VirtualConsole } = require('jsdom');
const vc = new VirtualConsole();
vc.on('jsdomError', (e) => console.log('Error:', e.message));
const dom = new JSDOM(fs.readFileSync(process.argv[2], 'utf8'), {
runScripts: 'dangerously',
virtualConsole: vc,
});
const btn = dom.window.document.querySelector('#copy');
console.log('Before click:', btn.textContent);
btn.click();
console.log('After click:', btn.textContent);node check.js btn.htmlBefore click: Copy
After click: CopiedThe text changes to [Copied] after the click, which confirms that the button script works.
A single typo in the script changes the result. I ran the same check on a file where the capital I in getElementById was replaced with a lowercase l.
sed 's/getElementById/getElementByld/' btn.html > btn-broken.html
node check.js btn-broken.htmlError: Uncaught [TypeError: document.getElementByld is not a function]
Before click: Copy
After click: CopyAn error is printed on the first line and the script stops, so the text stays [Copy] even after the click.
I gave the button a width of 120px and a height of 40px with CSS and then printed its size.
cat size.jsconst { JSDOM } = require('jsdom');
const dom = new JSDOM('<button id="copy" style="width:120px;height:40px">Copy</button>');
const btn = dom.window.document.querySelector('#copy');
console.log('height:', btn.offsetHeight, '/ width:', btn.getBoundingClientRect().width);node size.jsheight: 0 / width: 0jsdom has no step that draws on the screen, so the height and width are printed as 0.
🔢 FAQ & Recommended Content







ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.