skip to content

Capability Negotiation

The new-session payload asks for a browser and the remote end either matches it or refuses to start. Interviewers probe it whenever a session dies before the first line of the test runs.

on this pageshow

explore

questions

3

In Selenium 4, how does a remote end combine alwaysMatch and firstMatch to create a session?

level: middleimportance: must knowfreq 62%

answer

  1. Two members, not one map
  2. Alternatives are merged one at a time
  3. One candidate per alternative entry
  4. A duplicated key is rejected outright
  5. Order decides; nothing servable means no session

basics

~20 s

The remote end merges alwaysMatch into every firstMatch entry in order and starts the first merged set it can support. A key repeated in both is an error, and no match at all means session not created.

solid answer

~50 s

A Selenium 4 new-session request carries one `capabilities` object with two members: `alwaysMatch`, a single map of requirements every candidate session must satisfy, and `firstMatch`, an ordered array of alternative maps. The remote end validates the payload, then merges `alwaysMatch` into each `firstMatch` entry to produce one candidate per entry; if a name appears in both, merging fails with `invalid argument` rather than one value winning. It then walks the candidates in order and takes the first it can actually provide, so `firstMatch` expresses preference, not a union of extras. An omitted key is not a constraint - leaving out `browserVersion` means any version is acceptable. If `firstMatch` is absent it is treated as a single empty entry, so an `alwaysMatch`-only request is still one candidate. When nothing matches, the answer is `session not created`, which the Java client raises as `SessionNotCreatedException`.

code

json · 13 lines
json
{
  "capabilities": {
    "alwaysMatch": {
      "browserName": "chrome",
      "acceptInsecureCerts": true,
      "timeouts": {"implicit": 0, "pageLoad": 60000, "script": 20000}
    },
    "firstMatch": [
      {"platformName": "linux"},
      {"platformName": "windows"}
    ]
  }
}

go deeper

for a junior

Be ready to say that a session request carries alwaysMatch and firstMatch, that alwaysMatch holds requirements, and that firstMatch holds alternatives the remote end tries in order.

for a middle

Explain the merge itself: one candidate per firstMatch entry, a name appearing in both members rejected as invalid argument, and the first servable candidate winning rather than the best one.

for a senior

Show how you diagnose a run that never started, separating a malformed payload from a well-formed request nothing could serve, and reading the negotiated capabilities back out of the response.

for a principal

Own the fleet-level tradeoff: which conditions are worth a failed run, and how a fallback to a later alternative stays visible in reports instead of silently changing what was tested.

## The shape of the new-session payload A Selenium 4 session starts with a single `POST /session` whose body carries one **`capabilities`** object, and that object has exactly two members that drive negotiation: **`alwaysMatch`**, one JSON object of requirements every candidate must satisfy, and **`firstMatch`**, an ordered JSON array of alternative objects. Nothing else in the request selects a browser. A suite that drives a support-ticket queue and needs Chrome on Linux is asking for it here, and if the ask cannot be honoured the run dies before its first navigation to the queue page. ```json { "capabilities": { "alwaysMatch": {"browserName": "chrome", "acceptInsecureCerts": true}, "firstMatch": [{"platformName": "linux"}, {"platformName": "windows"}] } } ``` Read aloud, that request says: Chrome, tolerating the ticket-queue staging server's self-signed certificate, on Linux if you have it, otherwise Windows. ## Merging produces one candidate per firstMatch entry The remote end does not treat `firstMatch` as extra options to sprinkle over a single request. It **merges** `alwaysMatch` into each entry separately, producing exactly as many candidate capability sets as there are entries. The payload above yields two: 1. `browserName: chrome`, `acceptInsecureCerts: true`, `platformName: linux` 2. `browserName: chrome`, `acceptInsecureCerts: true`, `platformName: windows` Merging carries one hard rule: **a name may not appear in both `alwaysMatch` and the `firstMatch` entry being merged**. There is no precedence and no last-writer-wins. The merge fails, and the whole request is rejected with the **`invalid argument`** error, which the Java client raises as `InvalidArgumentException`. Writing `browserName` into `alwaysMatch` and again into one alternative is the usual way to trip it. When `firstMatch` is absent it is treated as an array holding one empty object, so an `alwaysMatch`-only request still produces exactly one candidate. That is why an ordinary request naming a browser and nothing else goes through the same negotiation as an elaborate one. ## Selection walks the candidates in order The remote end takes the candidates in order and returns the **first one it can actually provide**. That ordering is the whole point of the name: `firstMatch` expresses preference, not a union of acceptable extras, and the second entry is consulted only when the first cannot be served. Two consequences follow for a ticket-queue suite: - Put a condition in `alwaysMatch` and you have said *no session at all* rather than accept anything else. - Put alternatives in `firstMatch` and you have said *degrade quietly*, which is how a nightly run can move to a different platform without anyone noticing. - Leave a key out and you have constrained nothing. Omitting `browserVersion` accepts every version the other side offers; there is no implicit default that quietly narrows the request. ## Which keys are compared, and which are only applied Not every capability takes part in matching. The specification singles out a few for comparison and treats the rest as configuration applied once a session has been agreed. | Capability | Part it plays | |---|---| | `browserName` | compared; a mismatch eliminates that candidate | | `browserVersion` | compared, by a comparison the remote end defines | | `platformName` | compared as a string; omit it to accept any operating system | | `acceptInsecureCerts` | compared; asking `true` of an end that cannot do it eliminates the candidate | | `pageLoadStrategy`, `timeouts` | not filters; configuration applied to the session that gets created | | `goog:chromeOptions`, `moz:firefoxOptions`, `se:` keys | extension capabilities, namespaced by the prefix before the colon | ## What the response gives back On success the response's `value` holds a `sessionId` and a second `capabilities` object describing the session that was actually created: the real `browserVersion`, the real `platformName`, and whichever extension keys the driver chose to report. It is a report, not an echo. Asking for Chrome without naming a version and getting one back with `browserVersion` filled in is normal and expected. In Java that negotiated set is read from `HasCapabilities.getCapabilities()`, implemented by `RemoteWebDriver`, and the returned `Capabilities` exposes `getBrowserName()`, `getBrowserVersion()` and `getPlatformName()`. Worth recording with every ticket-queue result: - `browserName` and `browserVersion`, so a failure is attributable to a build rather than to "the browser". - `platformName`, because a `firstMatch` alternative may have moved the run somewhere else. - the `sessionId`, which ties your result to the remote end's own logs for the same session. ## When negotiation fails If no candidate can be served, the remote end answers with **`session not created`**, and the Java client raises `SessionNotCreatedException`. Separating that from the other pre-session failure is most of the diagnosis: - `invalid argument`, returned with HTTP 400, means the payload itself is wrong, most often a name duplicated across `alwaysMatch` and a `firstMatch` entry. Adding more alternatives will never fix it. - `session not created`, returned with HTTP 500, means the payload was well formed and nothing on the other side could satisfy any candidate. Loosening `alwaysMatch` or adding an alternative is the fix. Because all of this happens before a driver object exists, the failure has no screenshot, no page source and no browser log behind it. The only evidence is the request you sent and the error you got back, which is exactly why a suite should log the payload it negotiates with.

  • What happens if firstMatch is left out of the request entirely?
    It is treated as an array holding one empty object, so `alwaysMatch` is merged with nothing and becomes the single candidate. A request that names only a browser still goes through the same algorithm; there is simply one candidate to try, and if it cannot be served the answer is `session not created`.
  • Why is putting browserName in both alwaysMatch and a firstMatch entry an error rather than a redundancy?
    Merging defines no precedence between the two members, so the protocol refuses to guess which value wins. The remote end rejects the request with `invalid argument`, raised by the Java client as `InvalidArgumentException`. Keep shared requirements in `alwaysMatch` and let each `firstMatch` entry carry only the keys that differ between alternatives.
  • From a failing run, how do you tell negotiation from a browser problem?
    Read the error. `session not created` means no candidate could be served, so no browser was ever driven and there is no screenshot or page source to collect. Anything raised later means a session existed. Logging the negotiated capabilities from the response separates "we never started" from "we started on the wrong browser".

It works like a job advert with non-negotiable requirements plus a ranked list of acceptable offices: every candidate must meet the requirements, and the first office that can actually seat someone is the one that gets filled.

saying these in an interview costs you the question

  • Says firstMatch adds extra capabilities on top of alwaysMatch instead of forming separate candidates
  • Thinks a key in both members is resolved by precedence rather than rejected
  • Believes the remote end picks the best-matching candidate rather than the first servable one
  • Assumes an omitted capability is defaulted rather than simply unconstrained
  • Confuses invalid argument with session not created and adds alternatives to fix a malformed request
open as a page

In Selenium 4, which capability keys does a new-session request use to ask for a specific browser?

level: juniorimportance: should knowfreq 68%

basics

~10 s

browserName names the browser, browserVersion asks for a build, platformName asks for an operating system, and acceptInsecureCerts asks to tolerate bad certificates. Any key you leave out places no constraint at all.

open as a page

In Selenium 4, how do you decide which capabilities belong in alwaysMatch and which belong in firstMatch?

level: principalimportance: should knowfreq 33%

basics

~10 s

Put in alwaysMatch only what a result would be meaningless without, and use firstMatch for preferences you would rather degrade than fail on. Every extra requirement trades a chance of no session for reproducibility.

open as a page