If a URL opens in your browser but cy.visit() reports “page not found,” do not assume Cypress is broken. First compare the exact request Cypress made with the address you typed manually, then inspect its status code, redirects, authentication state, and server routing. The usual causes are a different URL (often a slash or base-path difference), a protected route, a single-page-app history route without a production fallback, or an environment-specific proxy problem.
Start with the request Cypress actually made
A browser can finish on a page that looks correct after redirects, client-side navigation, or an existing login session. Cypress reports what happened during its own request. Capture that request and response before changing configuration.
1. Compare the complete URL
Print or inspect the final URL, including protocol, hostname, port, path, query string, hash, and trailing slash. Compare it character-for-character with the address-bar URL that works.
describe('route diagnostic', () => {
it('visits the intended URL', () => {
cy.visit('/inventory.html')
cy.url().then(url => cy.log(`Cypress URL: ${url}`))
})
})
Check the baseUrl in cypress.config.js (or the equivalent TypeScript configuration) and the path passed to cy.visit():
#1 Best Overall
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'https://example.test'
}
})
A relative path is resolved against baseUrl. A leading slash replaces the path portion of that base URL, while a path without a leading slash is resolved differently. Make the intended result explicit and avoid accidental double slashes or missing subpaths.
2. Inspect status codes and redirects
Use the browser’s Network panel, your server access log, or a command-line request to see the response for the exact Cypress URL. A manually entered address may redirect to the site root, login page, or another route before rendering something that appears successful. A 404 for the original path can therefore be hidden by the final page.
cy.request({
url: '/inventory.html',
failOnStatusCode: false
}).then(response => {
cy.log(`status: ${response.status}`)
cy.log(`redirected: ${response.redirected}`)
expect(response.status).to.be.oneOf([200, 301, 302, 307, 308])
})
cy.request() is a diagnostic aid; it does not replace a real browser visit when you need to test rendering. If the response is a redirect, follow the chain and determine whether the destination is expected.
Rank #2
Branch by the failure you observe
The URL differs from the manual visit
- Correct the
baseUrl, environment variable, port, or path spelling. - Check whether the application is mounted under a prefix such as
/app. - Decide whether the trailing slash is significant for your server or CDN and use one canonical form.
- Verify that the test is running in the same environment (local, staging, or production) as the manual check.
For example, if the working address is https://example.test/app/inventory.html, a baseUrl of https://example.test combined with cy.visit('/inventory.html') requests the wrong location.
Recommended Free Tools
The route redirects to login
A protected page can be reachable manually because your browser already has a session. Cypress starts with a separate browser profile, so the same URL may redirect to a login page or return an authorization error. Establish authentication before visiting the protected route, then assert the result.
cy.session('test-user', () => {
cy.visit('/login')
cy.get('[name=email]').type(Cypress.env('E2E_EMAIL'))
cy.get('[name=password]').type(Cypress.env('E2E_PASSWORD'), { log: false })
cy.get('button[type=submit]').click()
cy.url().should('include', '/dashboard')
})
cy.visit('/inventory.html')
cy.url().should('include', '/inventory.html')
Use your application’s supported login flow or a documented API-based setup. Do not copy real session cookies or credentials into source control. If authentication is not the cause, remove this branch from your diagnosis rather than adding unnecessary login code.
Rank #3
The app uses history-mode client routing
In a single-page application, a route such as /todos/42 may be resolved by the client router after index.html loads. A development server often supplies that fallback automatically. A simple production static server may instead look for a physical todos/42 file and return 404. Configure the production server, reverse proxy, or CDN to serve index.html for non-file routes; then let the client router resolve the path.
Do this only when the application uses history-style routing. Hash routes (for example, /#/todos/42) are requested as the same entry document and usually do not require the same server fallback. After changing the server, test a direct request to the deep link—not only a click from the home page.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsThe failure occurs only on localhost or one browser
An older Cypress issue described intermittent 404s while visiting different localhost ports with Cypress 3.0.1 on Windows 10 and Chrome. A later comment attributed a similar symptom to Chrome bypassing Cypress’s proxy for loopback addresses and mentioned --proxy-bypass-list=<-loopback> as a workaround. This is historical, version-specific evidence, not a current universal fix.
Rank #4
- Record the Cypress version, browser version, operating system, and every localhost port.
- Reproduce with the current Cypress and browser versions.
- Compare the request in Cypress with a direct command-line or browser request.
- Only then test a proxy setting in an isolated run, and document how to remove it.
Do not add a proxy flag merely because the error message resembles that old report.
Understand base paths and full-page navigation
Every framework has a base URI or equivalent setting used to resolve relative links. A relative URL can therefore work in one context and fail in another if the app is deployed below a subdirectory. Client-side navigation may never contact the server for a route, while a forced full-page load does. Use the framework’s documented base-path setting consistently in the build, server, and Cypress configuration.
When diagnosing, test both transitions:
// Client-side navigation from the entry page
cy.visit('/')
cy.get('a[href="/todos/42"]').click()
cy.url().should('include', '/todos/42')
// Direct server request to the same route
cy.visit('/todos/42')
If the click works but the direct visit fails, the server fallback or deployment base path is the leading suspect.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →A repeatable diagnostic checklist
- Save the exact
cy.visit()argument and resolvedbaseUrl. - Record the requested URL, status code, response headers, and redirect locations.
- Check whether the manual browser has a login session that Cypress lacks.
- Determine whether the route is a server file, an API endpoint, or a client-side history route.
- For an SPA route, verify that the production layer returns the entry document for a direct deep-link request.
- Compare local and remote hosts, ports, schemes, and proxy settings.
- Re-run with current Cypress and browser versions before applying old workarounds.
- Add a focused assertion so the test fails with the actual destination instead of a generic page-not-found message.
Common errors and targeted fixes
| What you observe | Likely explanation | Next action |
|---|---|---|
| Manual page is visible, Cypress URL ends at the root | Redirect from the requested path | Inspect redirect responses and authentication requirements |
| Manual visit works only while logged in | Cypress has no matching session | Create a test session, then assert the post-login URL |
Clicking a route works; direct cy.visit() returns 404 |
Missing history-mode fallback | Serve the SPA entry document for unknown non-file paths |
| Only a subdirectory deployment fails | Inconsistent base path | Align build, server, links, and baseUrl |
| Only one old Chrome/Cypress/localhost combination fails | Environment-specific proxy behavior | Reproduce on current versions before considering a proxy flag |
| Status is 200 but the page is blank | JavaScript or asset failure after the document loads | Inspect console and asset requests; this is not a simple 404 diagnosis |
Reliability and performance considerations
Keep URL and response diagnostics in a small, dedicated test or setup command so normal suites remain readable. Prefer a stable staging hostname over a changing localhost port when testing routing. If a page depends on asynchronous redirects or client rendering, wait for a specific element that proves the intended route loaded instead of relying only on a short timeout.
Use cy.intercept() to observe relevant API calls, but do not mistake a successful API response for a successful document request. A server can return healthy data while the HTML route still returns 404. Conversely, a valid HTML response can be followed by a failed JavaScript bundle request.
cy.intercept('GET', '**/api/**').as('api')
cy.visit('/todos/42')
cy.wait('@api')
cy.get('[data-testid="todo-page"]').should('be.visible')
Or skip the browser setup
If your goal is to generate a clean image or PDF of a URL rather than test Cypress routing, ScreenshotNeo provides a single HTTP request. 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, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for all 63 options, including full-page and element capture, device and viewport settings, JavaScript and CSS, waits, request blocking, cookies and headers, geolocation, PDFs, signed links, asynchronous jobs, bulk capture, caching, and usage reporting. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Frequently Asked Questions
Why does Cypress show a 404 when the browser displays the app shell?
The browser may have followed a redirect, used an existing login session, or loaded the SPA entry document before client-side routing. Inspect the original Cypress response and redirect chain to distinguish these cases.
Should I disable web security or add a proxy flag?
Not as a first step. Those settings address particular cross-origin or historical localhost conditions. Confirm the URL, response, authentication, routing fallback, and current software versions first.
How can I prove that the server supports a deep link?
Request the deep-link URL directly in a fresh session or with a command-line HTTP client and verify that the server returns the application entry document rather than a 404. Then run the same URL with cy.visit().
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.

