Recommended Free Tools
Raw HTML and rendered HTML answer different questions. Raw HTML is the response your server sends before JavaScript runs; rendered HTML is the browser’s resulting DOM after scripts, styles, data requests, and user interactions change the page. A reliable test captures both, compares required content and links, and—when search visibility matters—checks the URL with Google’s own inspection tools rather than assuming a local browser matches Google.
What raw HTML and rendered HTML mean
Raw HTML: the server response
Raw HTML is the document returned by an HTTP request. View Source normally shows this response, before the browser executes application JavaScript or builds a final DOM. It can contain headings, links, metadata, structured data, placeholders, or almost none of the page’s eventual content.
Fetch the response directly when you need to test what arrives over the network:
curl -L https://example.com/products/42 -o raw.html
Search raw.html for text that must be available immediately, canonical and alternate links, robots directives, JSON-LD, and navigational URLs. A successful HTTP request does not prove that those items are present.
#1 Best Overall
Rendered HTML: the post-script DOM
The rendered page is the DOM after the browser parses the response, executes JavaScript, applies application state, makes subsequent requests, and processes any interaction required to reveal content. Chrome’s Elements panel (Inspect) shows this live DOM, not necessarily the original response. A client-rendered page can therefore show a product title in Inspect while the same title is absent from View Source.
The DOM is state-dependent. A page loaded while signed out, at a mobile viewport, with a blocked API request, or before a menu is opened can produce a different result from the same URL after interaction.
A repeatable comparison workflow
- Define the required output. List the text, links, metadata, structured data, buttons, and state transitions that matter. Separate items that must be in the initial response from those allowed to appear after execution.
- Save the original response. Request the URL with redirects followed and record the final URL, status, headers, and response body. Search the body for each required item.
- Automate the same URL. Use a fixed browser target, viewport, locale, session state, and network policy. Wait for the application’s meaningful ready condition, such as a page-specific selector or completed API response, instead of a universal sleep.
- Capture the resulting DOM. Read
document.documentElement.outerHTML, then assert selectors and values. Also collect console errors and failed requests, because a visually incomplete page can otherwise look like a content assertion failure. - Compare by purpose. A difference between response and DOM is normal for a client-rendered application. The test should identify whether required content, destinations, metadata, and structured data exist in the state that users or crawlers need.
- Test alternate states. Repeat with a signed-out session, a fresh context, relevant viewport or device, and any interaction that reveals important content. If compatibility matters, run more than one browser engine.
JavaScript test with Playwright
Playwright can automate Chromium, WebKit, and Firefox, emulate devices, and use bundled browsers or branded Chrome and Edge channels. Choose targets that match the question: an application-compatibility test should cover the engines you support; a production diagnosis may need the branded channel your users run.
import { test, expect } from '@playwright/test';
const url = 'https://example.com/products/42';
test('raw response and rendered DOM contain the product contract', async ({ page, request }) => {
const response = await request.get(url, { failOnStatusCode: false });
const raw = await response.text();
expect(response.status()).toBe(200);
expect(raw).toContain('Product 42'); // Must be server-delivered
const consoleErrors = [];
const failedRequests = [];
page.on('console', message => {
if (message.type() === 'error') consoleErrors.push(message.text());
});
page.on('requestfailed', request => {
failedRequests.push(`${request.method()} ${request.url()} — ${request.failure()?.errorText || 'failed'}`);
});
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="product-ready"]').waitFor();
await expect(page.locator('h1')).toHaveText('Product 42');
await expect(page.locator('a[data-testid="buy-link"]')).toHaveAttribute('href', /checkout/);
const rendered = await page.locator('html').evaluate(node => node.outerHTML);
expect(rendered).toContain('Product 42');
if (consoleErrors.length || failedRequests.length) {
throw new Error(JSON.stringify({ consoleErrors, failedRequests }, null, 2));
}
});
Install Playwright with your project’s package manager and install the browser builds required by your test matrix. Keep the ready selector tied to application state; replacing it with waitForTimeout(5000) makes tests slow when the page is fast and flaky when it is slower.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesComparing the two documents programmatically
const normalize = html => html.replace(/s+/g, ' ').trim();
const rawText = normalize(raw);
const domText = normalize(rendered);
for (const required of ['Product 42', 'Shipping', 'checkout']) {
console.log(required, {
inRaw: rawText.includes(required),
inDom: domText.includes(required)
});
}
Prefer semantic assertions over a full-string DOM diff. Framework-generated attributes, whitespace, hydration markers, and serialized element order can change without affecting behavior. Save full documents as artifacts when a failure needs investigation.
Rank #2
Equivalent automation with Puppeteer
Puppeteer automates Chrome and Firefox, supports interaction and request interception, and is useful when your project already standardizes on its API.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const errors = [];
const failed = [];
page.on('console', message => {
if (message.type() === 'error') errors.push(message.text());
});
page.on('requestfailed', request => {
failed.push({ url: request.url(), error: request.failure()?.errorText });
});
const response = await page.goto('https://example.com/products/42', {
waitUntil: 'domcontentloaded'
});
if (!response || response.status() !== 200) {
throw new Error(`Unexpected status: ${response?.status()}`);
}
await page.waitForSelector('[data-testid="product-ready"]');
const rendered = await page.content();
const title = await page.$eval('h1', node => node.textContent.trim());
if (title !== 'Product 42') throw new Error(`Unexpected title: ${title}`);
if (errors.length || failed.length) throw new Error(JSON.stringify({ errors, failed }));
await browser.close();
How to test Google’s rendering
Google treats crawling, rendering, and indexing as separate stages. It uses rendered HTML for indexing, but a page can wait in a rendering queue. Blocked resources, JavaScript failures, unsupported browser features, and some non-200 responses can prevent or bypass rendering.
Search Console URL Inspection
For a property you manage, inspect the live URL in Search Console. Review the rendered result, loaded resources, JavaScript console output, exceptions, and indexing status. This is the appropriate diagnostic when the question is “What can Google access for this URL?” rather than “What did my local browser do?”
Rich Results Test
Use Rich Results Test for an eligible public URL when you need to examine rendered content and structured-data eligibility. The page must be reachable without login and must not be blocked by robots.txt. The tool is not a general guarantee of every Google Search result, but it exposes rendering evidence under Google’s test conditions.
What a local test cannot prove
A local Playwright or Puppeteer run proves only what that browser, version, viewport, session, network, and timing produced. It does not prove Google rendered the same DOM. Run both kinds of test when search visibility is a requirement.
Why content appears in the browser but not in source or search
It is client-rendered by design
The server may send an application shell while JavaScript fetches the actual article or product. That explains a missing View Source match, but it still requires successful execution and indexing.
A resource is blocked or fails
Check robots rules, CSP, firewall decisions, authentication, API status codes, CORS, JavaScript exceptions, and failed requests. A single blocked data request can leave a valid shell with no meaningful content.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteThe response status is unsuitable
Redirect chains, error responses, soft-error pages, and non-200 responses can affect whether Google renders or indexes a URL. Record the final status and URL in every test.
The browser feature is unavailable
Unsupported APIs, incorrect user-agent assumptions, web-component errors, and engine-specific behavior can produce different output. Test Chromium, WebKit, and Firefox when cross-engine support is part of the product contract.
Stale assets are being used
Google notes that its rendering system may ignore caching headers and can use outdated JavaScript or CSS. Fingerprinted asset filenames help ensure a new deployment is distinguishable from an older one.
Rank #4
View Source versus Inspect Element
| Question | View Source | Inspect (Elements) |
|---|---|---|
| What does it show? | Usually the original HTTP response | The current browser DOM |
| Has JavaScript run? | No, in the normal source view | Yes, including later mutations |
| Best use | Server-rendering, initial metadata, links, and JSON-LD | Final content, state, interaction, and layout structure |
| Main limitation | Cannot show content created later by scripts | Does not reveal what the server originally sent |
Reliability, performance, and cost considerations
- Use deterministic readiness: selector-based or application-signaled readiness is more reliable than fixed delays.
- Control variability: pin browser versions where practical, set viewport and locale explicitly, and isolate storage between tests.
- Capture diagnostics: retain status, final URL, console errors, failed requests, screenshots, raw HTML, and rendered DOM for failed runs.
- Separate smoke and compatibility suites: run a fast critical-path test on every change and broader engine/device coverage on a schedule or release gate.
- Throttle realistically: network conditions and API latency can expose race conditions that a local fast connection hides.
- Do not equate a screenshot with content verification: pixels show appearance; DOM assertions verify text, links, metadata, and state.
Or skip the browser setup
For repeatable screenshots, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
One GET request returns PNG, JPEG, WebP, or PDF:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for all options. Python and Node.js equivalents:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
It also supports full-page lazy-image capture, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, an OpenAPI specification, and familiar parameter names for easier migration. Its MCP server supplies take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting checklist
Raw text is missing
Confirm you fetched the final URL, followed redirects, inspected the response body rather than a cached fixture, and verified the server actually renders the required content. If it is intentionally client-rendered, move the assertion to the browser test or add server-side rendering for critical content.
Free tools Windows power users keep installed
One-click scans. No signup required.
The ready selector never appears
Inspect console output and failed requests, verify the selector exists in the expected state, and check authentication, feature flags, API responses, and viewport-dependent branches. Avoid increasing a timeout until the cause is known.
Best Value
Local DOM passes but Google does not show the content
Run URL Inspection or Rich Results Test. Check robots.txt, resource loading, response status, exceptions, redirects, and whether the important content appears only after an interaction Google will not perform.
Tests pass in Chromium but fail elsewhere
Run the same assertions in the other supported engines, then isolate feature detection, CSS differences, timing assumptions, and browser-channel differences. Fix the application or narrow the documented support matrix; do not silently treat one engine as universal.
Architecture choices for search-critical content
Server-side rendering or prerendering places important content in the initial response, improving speed and helping crawlers that cannot run JavaScript. Google describes dynamic rendering as a workaround rather than a long-term solution for JavaScript-generated content. Prefer a rendering architecture that serves a complete, canonical response when practical, while retaining browser tests for interactive behavior.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Frequently Asked Questions
Can I use a DOM snapshot as proof that Google indexed my text?
No. A snapshot proves what your selected browser produced under its test conditions. Google’s URL Inspection or Rich Results Test is needed for Google-specific rendering evidence, and indexing can still depend on crawling and other search systems.
Should metadata and structured data be tested in both documents?
Yes when they are search-critical. Assert their presence and values in the original response if they must be available immediately, then verify the final DOM when scripts can modify them.
Is a screenshot enough for a rendering regression test?
No. Screenshots detect visual changes, but DOM assertions are needed to verify exact text, links, metadata, structured data, and interaction state.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

