skip to content

In Playwright, how does the baseURL option resolve the path you pass to request.get()?

level: middleimportance: should knowfreq 38%

answer

  1. Not string concatenation
  2. The URL constructor decides
  3. An absolute URL wins outright
  4. Trailing slash changes everything
  5. Read the URL the response reports

basics

~20 s

Playwright combines them with the URL constructor, not by string concatenation. An absolute URL wins outright, a leading-slash path replaces the base's whole path, and a relative path resolves against the base, where a missing trailing slash drops the base's last segment.

solid answer

~40 s

`baseURL` comes from the config's `use` block, from `test.use`, or from the options you pass to `playwright.request.newContext()`, and the `request` fixture honours it. Resolution follows the JavaScript `URL()` constructor: `new URL(path, baseURL)`. So with `baseURL: 'https://weather.example.com'`, `request.get('/api/forecast')` hits `https://weather.example.com/api/forecast`. A full URL passed to the call ignores `baseURL` entirely, which is how you reach a third-party host from the same test. The sharp edge is relative paths: with `baseURL: 'http://localhost:3000/api'` — no trailing slash — `request.get('./forecast')` resolves to `http://localhost:3000/forecast`, because the last segment is treated as a file, not a directory. Add the trailing slash, or use leading-slash paths and avoid the question.

code

typescript · 12 lines
typescript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    // request.get('/api/forecast') -> https://weather.example.com/api/forecast
    baseURL: process.env.WEATHER_BASE_URL ?? 'https://weather.example.com',
    extraHTTPHeaders: {
      Authorization: `Bearer ${process.env.FORECAST_TOKEN}`,
      Accept: 'application/json',
    },
  },
});

go deeper

for a junior

Recall that setting baseURL lets calls pass a path such as /api/forecast, and that a full URL in the call is used as is.

for a middle

Explain that resolution uses the URL constructor, and work through why a base without a trailing slash loses its last segment when a relative path is passed.

for a senior

Show how the option is driven per environment in CI, and diagnose a 404 by reading the URL the response reports rather than the one written in the test.

for a principal

Own the convention across the suite, so environments and prefixes are configured in one place and no test hardcodes an origin that pins it to one deployment.

`baseURL` lets tests say `/api/forecast` instead of repeating an origin in every call, and lets the same suite run against local, staging and preview environments by changing one config value. The rules are not string concatenation, and the difference bites. ## Where baseURL comes from Three places, all producing the same behaviour for the HTTP client: - The `use` block of the Playwright config, which the `request` fixture inherits. - `test.use({ baseURL })`, narrowing it to a file or a `describe` block. - The options object of `playwright.request.newContext({ baseURL })` when you build a context yourself. The same option name also steers page navigation, so one value can serve both the browser and the HTTP calls in a suite. In a CI pipeline it is usually fed from an environment variable so a preview deployment can be targeted without touching test code. ## The URL() rules Playwright resolves each call as `new URL(pathYouPassed, baseURL)`. That gives four cases: | baseURL | passed to request.get() | resulting URL | |---|---|---| | `https://weather.example.com` | `/api/forecast` | `https://weather.example.com/api/forecast` | | `http://localhost:3000/api/` | `./forecast` | `http://localhost:3000/api/forecast` | | `http://localhost:3000/api` | `./forecast` | `http://localhost:3000/forecast` | | `https://weather.example.com` | `https://api.vendor.test/v1/now` | `https://api.vendor.test/v1/now` | Read the third row twice. A base whose path has no trailing slash treats its last segment as a document rather than a directory, exactly as a relative link in an HTML page would, so the relative path replaces it. This is the single most common surprise, and it shows up as a 404 against a URL nobody in the test wrote. ## Practical consequences - A leading `/` always anchors at the origin and throws away the base's path. If your base carries a path prefix such as `/api`, leading-slash paths will skip it. - An absolute URL is used verbatim. Calling a third-party forecast provider directly from a test needs no extra configuration. - `baseURL` does not add headers, authentication or a prefix of any other kind — it only builds URLs. - If `baseURL` is unset and a call is given a path, resolution fails; a test that passes locally and breaks in CI is often a missing environment variable. ## Its companion options `baseURL` is rarely alone. The same `use` block usually carries `extraHTTPHeaders`, which attaches a header — most often `Authorization` or `Accept` — to every call the client makes, with per-call `headers` merged on top for the exceptions. `httpCredentials` handles HTTP basic auth, `ignoreHTTPSErrors` covers self-signed certificates in test environments, and `proxy` routes the traffic. Configuring these once is what keeps individual tests down to a path and a payload. ## Choosing a convention Two conventions both work; pick one and hold it: 1. **Origin-only base, absolute paths.** Set `baseURL: 'https://weather.example.com'` and always call `/api/forecast`. Simple, no trailing-slash questions, and the `/api` prefix is visible at every call site. 2. **Prefixed base, relative paths.** Set `baseURL: 'https://weather.example.com/api/'` — with the trailing slash — and call `forecast`. Shorter call sites, but every author must remember the slash and never use a leading `/`. Mixing the two produces URLs that look right in the source and wrong on the wire. ## Debugging a mis-resolved call When a call 404s unexpectedly, read the URL Playwright actually used rather than the one you typed: `response.url()` returns it, and the failure output of `await expect(response).toBeOK()` includes the request log. Nine times out of ten the answer is a missing trailing slash on the base or a leading slash on the path.

  • What happens in Playwright when a call passes a full URL while baseURL is set?
    The absolute URL is used as given and `baseURL` is ignored for that call, because `new URL(absolute, base)` returns the absolute one. That is how a test hits a third-party forecast provider without a second context. Configured `extraHTTPHeaders` still apply, so be careful not to leak your own token to another host.
  • How do per-call headers interact with extraHTTPHeaders in Playwright?
    Per-call `headers` are merged over the context's `extraHTTPHeaders`, so a call can override one header — a different `Accept`, or a second user's token — without discarding the rest. Header names are case-insensitive when merging, so `authorization` and `Authorization` are the same key.

saying these in an interview costs you the question

  • Thinks baseURL and the path are simply concatenated
  • Expects a leading slash path to keep the base's path prefix
  • Assumes an absolute URL is rewritten onto the base origin
  • Believes baseURL also adds authentication headers
  • Never checks the URL the response actually reports