World’s largest virtual agentic engineering & quality conference
DPI, PPI, and screen resolution explained for testers: what device pixel ratio means in code, why 1 CSS pixel is not 1 hardware pixel, and how to test both.

Aakash Rao
Author

Harish Rajora
Reviewer
Last Updated on: August 6, 2026
A designer files a bug that a logo looks soft on their phone. You open the same page, resize your browser to a phone width, and everything looks fine. Both of you are right, because resizing a desktop browser changes the viewport and leaves the screen density alone.
That gap between viewport and density is where the soft-logo, blurred-canvas, and failed-screenshot bugs come from. This guide covers how DPI and screen resolution relate, separates the three ideas that get collapsed into the word pixel, and shows how to read and control density in a real test run.
Overview
Screen resolution counts the pixels a display holds. DPI describes how tightly those pixels are packed into one physical inch, and the CSS pixel your stylesheet writes is a third unit again. Two screens can share a resolution and look completely different, because density rather than raw pixel count decides how sharp the result appears.
What is the difference between DPI and screen resolution?
How do you test a website at different screen resolutions?
Set the viewport and the density as two separate variables, because changing one does not change the other. A Playwright browser context accepts a viewport size and a deviceScaleFactor together, which reproduces a high density screen inside CI. Emulation catches layout mistakes, while TestMu AI adds real hardware where font rendering and asset selection behave as they do for users.
Screen resolution is the number of pixels a display can address, written as horizontal count by vertical count. A 1920x1080 panel carries 1920 pixels across and 1080 down, and multiplying the two gives just over two million addressable points.
Resolution on its own says nothing about physical size. The same 1920x1080 grid can stretch across a 32 inch monitor or compress into a 13 inch laptop, and the pixels in the laptop are far smaller. That is the first place the word pixel starts doing two jobs at once.
For testing purposes, treat resolution as a property of the hardware. It sets the ceiling on how much detail a screen can show, and it is the number that marketing names like Full HD and 4K describe. If you need the specific figures that dominate current traffic, our reference on common screen sizes and resolutions tracks them by device class.
DPI stands for dots per inch. It originated in printing, where it counts how many individual ink dots a printer lays down along one inch of paper, and a higher figure means finer detail in the printed output.
Screens do not print dots, so the accurate term for a display is PPI, or pixels per inch. The distinction matters in a print workflow and blurs almost everywhere else, because the software you test against kept the older label.
Android is the clearest example. Its documentation defines a density independent pixel against a screen measured in dpi, stating that one dp is a virtual pixel unit that is roughly equal to one pixel on a medium density screen at 160 dpi, per the Android guide to supporting different pixel densities. When an API label says dpi, check which quantity it measures before converting, because Android's figure counts screen pixels rather than ink dots.
Resolution counts pixels, density describes their spacing, and the CSS pixel is an abstraction layered on top of both.
| Attribute | Screen resolution | PPI (or DPI on screens) | CSS pixel |
|---|---|---|---|
| What it measures | Total addressable pixels across the panel, width by height | How many of those pixels fall within one physical inch | A layout unit defined by visual angle, not by hardware |
| Changes with panel size | No, the pixel count is fixed by the hardware | Yes, a smaller panel at the same resolution has higher density | No, CSS fixes 1in at 96px; only the reference pixel's physical size varies, and with viewing distance rather than panel size |
| Readable from JavaScript | Only indirectly, since screen.width itself reports CSS pixels and must be multiplied by the device pixel ratio | No, the browser exposes a ratio rather than a physical density | Yes, window.innerWidth reports the CSS viewport |
| What breaks when ignored | Layouts that assume a fixed canvas size | Soft logos and blurred canvas output on dense screens | Media queries written against hardware pixel counts |
The unit your stylesheet writes is not a hardware pixel. The W3C CSS Values and Units Module Level 4 defines the reference pixel as the visual angle of one pixel on a device with a device pixel density of 96dpi and a distance from the reader of an arm's length.
Because the definition is an angle rather than a length, its physical size scales with viewing distance. The same specification notes that a reading distance of 71 cm gives a reference pixel of 0.26 mm, while a distance of 3.5 m gives one of 1.3 mm. A billboard pixel and a phone pixel can both be correct.
From that anchor, CSS fixes its absolute units against each other: 1in equals 96px, so one CSS pixel is 1/96th of an inch. The specification is candid about why the ratio was frozen there, noting that the change was made because too much existing content relies on the assumption of 96dpi, and breaking that assumption broke the content.
The practical consequence is that 96 is a definitional constant, not a measurement of anyone's monitor. Any test that treats a CSS pixel as a hardware pixel is testing an assumption the specification explicitly abandoned.
Note: Screen density behaves differently on real hardware than in a resized desktop window. TestMu AI runs your pages on 10,000+ real Android and iOS devices with no emulation layer in between. Try it free!
Every platform exposes density as a multiplier rather than a raw inch measurement. The browser calls it device pixel ratio, Android exposes it as DisplayMetrics.density and groups devices into named density buckets, and Apple calls it a scale factor, but all three answer the same question: how many physical pixels are spent drawing one logical unit.
In the browser, MDN documents window.devicePixelRatio as returning the ratio of the resolution in physical pixels to the resolution in CSS pixels for the current display device. MDN adds that a value of 1 indicates a classic 96 DPI display, while a value of 2 is expected for HiDPI and Retina displays.
Treat it as a float that can change mid session, because dragging a window between a laptop screen and an external monitor changes the display it reports on. For the browser support detail and the edge cases where the value misleads, see our reference on devicePixelRatio browser support and limits.
Android groups the hardware into named buckets so that one layout serves many devices. The Android documentation puts the medium density baseline at 160 dpi, places the xhdpi bucket at roughly 320 dpi, and the xxxhdpi bucket at roughly 640 dpi, which are 2x and 4x the baseline.
The conversion is arithmetic you can check by hand. Android states it as px = dp * (dpi / 160), so a 24dp tap target occupies 48 physical pixels on a 320 dpi device and 96 on a 640 dpi device.
For bitmap assets, the same documentation specifies a 3:4:6:8:12:16 scaling ratio across the six primary densities. An icon exported at only one size will be upscaled somewhere in that range, and upscaling is what the designer sees as softness.
Apple solves the same problem with points instead of buckets. In UIKit the default logical coordinate space is measured in points, and Apple documents UIScreen.scale as the factor that converts points into the screen's device coordinate space.
Apple states that for Retina displays the scale factor may be 3.0 or 2.0, meaning one point is drawn by nine or four pixels respectively, and that standard resolution displays use a scale factor of 1.0 where one point equals one pixel. On macOS the equivalent property is NSScreen.backingScaleFactor, described by Apple as the number of backing store pixels corresponding to each linear unit in screen space.
Points on iOS and dp on Android are the same idea under different names, and both line up with the CSS pixel when the page sets width=device-width at initial scale 1.
The values below were read from a running browser rather than derived. They come from a Playwright session on TestMu AI cloud browsers on 6 August 2026, recorded under build 99971055, where window.screen matched the requested 1280x720 viewport.
Both desktop cloud browsers reported a device pixel ratio of exactly 1:
Chrome / Windows 11 DPR=1 cssViewport=1280x720 screen=1280x720 physical=1280x720
Edge / Windows 11 DPR=1 cssViewport=1280x720 screen=1280x720 physical=1280x720A standard desktop grid session runs at 1x. No amount of resizing that window produces a 2x or 3x code path, so a responsive check performed only that way never exercises density at all.
Density has to be requested explicitly. Page zoom is the one exception, since Chromium multiplies the device pixel ratio by the zoom level, but zoom is no substitute because it rescales the CSS viewport at the same time. In Playwright, density is a browser context option set alongside the viewport:
// Emulate a 3x phone screen on a TestMu AI cloud browser
import { Browser } from "@testmuai/browser-cloud";
const client = new Browser();
const session = await client.sessions.create({
adapter: "playwright",
lambdatestOptions: {
browserName: "Chrome",
browserVersion: "latest",
"LT:Options": {
username: process.env.LT_USERNAME,
accessKey: process.env.LT_ACCESS_KEY,
platform: "Windows 11",
build: "Density check",
},
},
});
const { browser } = await client.playwright.connect(session);
const context = await browser.newContext({
viewport: { width: 393, height: 852 },
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
});
const page = await context.newPage();
await page.goto("https://www.testmuai.com/selenium-playground/");
const metrics = await page.evaluate(() => ({
dpr: window.devicePixelRatio,
cssWidth: window.innerWidth,
matches3x: window.matchMedia("(min-resolution: 3dppx)").matches,
}));
console.log(metrics);Run against the same cloud session, that context reported a different set of values from the default one:
BASELINE DPR=1 css=1280 min-resolution:2dppx=false 3dppx=false
OVERRIDE DPR=3 css=393 min-resolution:2dppx=true 3dppx=trueTwo details in that output matter. The resolution media queries stayed false at 1x and flipped to true at 3x, which means any CSS breakpoint written with a min-resolution query is dead code in a default desktop run.
The second detail is the arithmetic: 393 CSS pixels at a ratio of 3 occupy 1179 physical pixels. That is exactly the short edge of the iPhone 15 Pro, which Apple lists at 2556 by 1179 pixels. A guide that prints 393x852 in a table headed hardware resolution has mixed up the two units, and the gap between them is a factor of three.
Density defects cluster into a small number of shapes. Knowing the shapes turns a vague report that something looks off into a specific check.
Every one of these passes a functional test suite. They are rendering defects, so they need a visual check that compares against a baseline captured at the same ratio, which is where per density baselines earn their cost.
Testing every density is wasteful, and testing one is how the bugs above ship. Three integer ratios plus one fractional case cover the realistic range, and the decision is about which of them your traffic contains.
Emulation is the right tool for the first pass, since a deviceScaleFactor context is fast and runs on every commit. It has a real limit: it changes the numbers the page reports, not the font rasterization, the GPU, or the browser build underneath.
Confirming the result needs hardware. TestMu AI's real device cloud runs the page on physical handsets, each with its own screen resolution, chipset, and OS skin, and its Responsive Web Testing sessions validate breakpoint behavior, font scaling, and tap target sizing on those form factors. Font rasterization, GPU compositing, and OS-level display scaling behave there as they do for the user, which a scale factor override cannot reproduce.
For the browser side of the matrix, TestMu AI's test automation cloud runs 3,000+ browser and OS combinations in parallel. Pair that breadth with the checks in our responsive design testing checklist so the density cases sit alongside the layout cases instead of in a separate pass.
Open your test suite and search for deviceScaleFactor. If it does not appear, every test you own runs at ratio 1, and the measurements in this article show what that hides.
Add one context at ratio 2 and one at ratio 3 to the pages that carry logos, icons, or canvas output. Capture a visual baseline per ratio rather than a shared one, so a 3x capture is never compared against a 1x reference.
Then confirm on hardware, because emulation reports the numbers while a device renders the glyphs. You can check any page against a live grid with the screen resolution test tool, wire the same capabilities into your framework using the automation capabilities documentation, and run the whole matrix in parallel on TestMu AI's test automation cloud.
Author
Aakash Rao is a Technical Writer and Developer with over 2 years of experience in front-end development and developer-focused content creation. He has authored 20+ technical blogs, tutorials, and documentation pieces, with hands-on expertise in HTML, CSS, JavaScript, React, Tailwind, Three.js, and tools like Git, Firebase, Figma, and Playwright. On TestMu AI (formerly LambdaTest), he has authored practical guides on CSS and front-end techniques, web accessibility, and visual testing. He applies a test-first mindset, using Jest and Playwright to embed automated testing early in the development cycle.
Reviewer
Harish Rajora is a Software Developer 2 at Oracle India with over 6 years of hands-on experience in Python and cross-platform application development across Windows, macOS, and Linux. He has authored 800 + technical articles published across reputed platforms. He has also worked on several large-scale projects, including GenAI applications, and contributed to core engineering teams responsible for designing and implementing features used by millions. Harish has worked extensively with Django, shell scripting, and has led DevOps initiatives, building CI/CD pipelines using Jenkins, AWS, GitLab, and GitHub. He has completed his post-graduation with an M.Tech in Software Engineering from the Indian Institute of Information Technology (IIIT) Allahabad. Over the years, he has emphasized the importance of planning, documentation, ER diagrams, and system design to write clean, scalable, and maintainable code beyond just implementation.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance