How to fix Playwright `net::ERR_NAME_NOT_RESOLVED` in `page.goto`?

Playwright

net::ERR_NAME_NOT_RESOLVED means the browser's DNS resolver could not find an IP address for the hostname in the URL. In Playwright this surfaces from page.goto() and is always a configuration or environment issue, not an application bug. The most common causes are a typo in the URL or baseURL, using a hostname that only exists on a VPN or internal network that isn't active in CI, or pointing at a local service with a .local or custom TLD that requires mDNS resolution unavailable inside containers.

Common mistake

test('loads staging app', async ({ page }) => {
  // Typo: 'staging' should be a subdomain, not a different TLD
  await page.goto('https://app.staging-example.com');
});

Also common in CI when BASE_URL is set locally but not in the CI environment:

// playwright.config.ts
export default {
  use: {
    baseURL: process.env.BASE_URL ?? 'https://staging.example.internal',
    //                                ^^^ .internal TLD — no external DNS
  },
};

The fix

Verify the hostname resolves from the environment running the tests, then fix config or network access:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    // Prefer IP addresses for local services to skip DNS entirely
    baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
  },
  webServer: {
    command: 'npm run start',
    url: 'http://127.0.0.1:3000',
    reuseExistingServer: !process.env.CI,
  },
});

For staging environments on internal networks:

// playwright.config.ts
export default defineConfig({
  use: {
    baseURL: process.env.CI
      ? 'https://staging.example.com'     // External DNS — resolvable from CI
      : 'https://staging.example.internal', // Internal DNS — resolvable locally
  },
});

For debugging, confirm DNS resolution from the CI runner directly:

# Run this in your CI pipeline before tests
nslookup staging.example.com
# or
curl -I https://staging.example.com

Why it works

Playwright relies on Chromium's built-in DNS resolver, which uses the operating system's resolver chain. In Docker containers, /etc/resolv.conf may not include corporate DNS servers or VPN-specific resolvers, so hostnames that work on a developer's machine fail in CI. Using IP addresses for local services bypasses DNS entirely. Splitting baseURL by environment ensures the correct hostname is used based on which DNS infrastructure is available.

Tips

  • If ERR_NAME_NOT_RESOLVED appears for localhost specifically, Chromium on some Linux configurations resolves localhost to ::1 (IPv6) while the server listens only on 127.0.0.1 (IPv4) — use 127.0.0.1 explicitly.
  • For services behind a VPN, confirm the VPN is active in the CI runner or switch to a publicly routable staging URL for CI testing.
  • Custom TLDs like .dev, .local, or .internal require specific DNS configuration that may not be present in ephemeral CI containers — use a real domain with a private DNS record or an IP address instead.
  • If DNS resolves but the connection still fails, the problem has moved to the TCP layer — see ERR_CONNECTION_REFUSED.