skip to content

In Selenoid (unmaintained), what does the s3KeyPattern capability decide about a pushed artefact?

level: middleimportance: should knowfreq 49%

answer

  1. the destination has an address too
  2. a pattern, not a file name
  3. the session can override the server's layout
  4. the placeholder list is closed
  5. spaces and case do not survive intact

basics

~20 s

It decides the object key the artefact is stored under, meaning both its prefix path and its name in the bucket. Set per session, it overrides the server-wide pattern for that session's files only, and it is expanded from placeholders.

solid answer

~50 s

Selenoid, unmaintained by its own README, builds each uploaded artefact's object key from a pattern rather than a fixed name. The server-wide default comes from `-s3-key-pattern`, and a session may override it with the `s3KeyPattern` capability, which wins for that session's files whenever it is non-empty. The pattern is expanded from placeholders drawn from the session itself: `$sessionId`, `$browserName`, `$browserVersion`, `$platformName`, `$quota`, plus `$fileName`, `$fileExtension`, `$fileType` and `$date`. `$fileType` carries the kind of artefact — a log, a recording, or another kind a build emits — which is how one pattern sorts them into separate prefixes. Expansion normalises as it goes, lower-casing most substituted values, the session identifier and source file name excepted, and replacing every space in the finished key with a hyphen. The whole feature exists only in a build made with the S3 build tag, which Selenoid's own documentation says is absent by default.

code

java · 12 lines
java
// Selenoid is UNMAINTAINED per its own README; read as a model of the mechanism.
// Selenium's RemoteWebDriver carrying Selenoid's own extension capability map.
ChromeOptions options = new ChromeOptions();
options.setCapability("selenoid:options", Map.of(
        "enableLog", true,
        "name", "port logistics dashboard smoke",
        // overrides the server's -s3-key-pattern for this session's files only
        "s3KeyPattern", "$quota/$date/$fileType/$sessionId$fileExtension"
));

WebDriver driver = new RemoteWebDriver(
        new URL(System.getenv("GRID_URL")), options);

go deeper

for a junior

Know that an artefact sent to object storage gets a key, and that the key is the only way anyone finds it again later. There are no folders to browse.

for a middle

Be ready to explain that the pattern is expanded from a fixed list of substitutions and that a per-session value overrides the server-wide one, which is the reverse of the usual precedence.

for a senior

Be ready to design a prefix order for how the bucket will actually be read, and to say why normalisation makes client-side key reconstruction fragile.

for a principal

Own the layout as a long-lived interface. Decide which axes belong to the operator and which to a session, because a bucket's prefix order is expensive to change once objects exist under it.

When artefacts leave the machine that produced them, what decides whether anyone can find them again is not the upload but the **key**. An object store has no folders and no search of its own; it has a flat namespace of keys listed by prefix. The key layout is therefore the index you get, and it is chosen once, at upload time. ## Where the pattern comes from Aerokube's Selenoid, unmaintained by its own README and read here as an open model of a mechanism hosted services implement privately, resolves the pattern in two steps: 1. The server-wide pattern comes from the `-s3-key-pattern` flag, whose default is simply the source file's own name. 2. If the session's capabilities carried a non-empty `s3KeyPattern`, that value is used instead for that session's files. The precedence runs one way only: a session can override the operator's layout, not the reverse. That is worth remembering, because the other control here runs the opposite way: `-save-all-logs` overrides a session's log capability. ## The placeholders, and what they can see The pattern is a plain string, and the substitutions it understands are a fixed list — the finished file, the session that produced it, the account it authenticated as, and the clock: - `$fileName` — the source file's base name, as it stands on disk after renaming. - `$fileExtension` — that file's extension, lower-cased, including the dot. - `$fileType` — the kind of artefact the upload event declared: a log or a recording on a stock build, and a further kind on a build that emits one. - `$sessionId` — the session's identifier. - `$browserName`, `$browserVersion`, `$platformName` — from the session's own capabilities. - `$quota` — the account name the session authenticated as, which in a routed cluster is the account a request router matched it to. - `$date` — the date the upload happens. The consequence that catches people is the limit of that list. **It is closed and hard-coded; nothing extends it.** Selenoid offers no placeholder for your build identifier, your branch or your pipeline run — nor for the test name the session itself declares — so if you want the key to carry one of those, the session has to fold it into the pattern string it sends rather than hope the server knows it. ## Normalisation, which is not cosmetic Expansion is not a straight substitution. Most substituted values are lower-cased, and after all substitutions every space anywhere in the finished key is replaced with a hyphen. A browser name containing a space therefore appears hyphenated and in lower case. The session identifier and the source file name are not lower-cased, so a key can legitimately mix cases across its segments. Keys are compared literally, so a tool that reconstructs an expected key from the capabilities it sent, without applying the same normalisation, builds a string that does not exist in the bucket. ## Designing a layout The pattern is written once and lived with, so think about how it will be read: | what you want to do | what the prefix has to lead with | |---|---| | find a given session's artefacts | the session identifier, early | | sweep a day's worth | the date, early | | separate teams or accounts | the account name, first | | separate one kind of artefact from another | the artefact type, before the name | A layout such as account, then date, then artefact type, then session identifier reads well from the outside in: an operator can list one account, narrow to a day, narrow to logs, and find a session. A layout that leads with the session identifier makes single-session lookup trivial and sweeping impossible, because there is no shared prefix to sweep by. ## What else rides the same channel The key pattern is not the only control on the push. Glob flags decide **which** artefacts travel at all, matched against the file's base name rather than its full path, so a deployment can ship logs and leave heavier artefacts on the host. That pairing keeps a channel built for several kinds of artefact from carrying all of them. ## A worked case An end-to-end suite for a port-logistics container-tracking dashboard runs nightly and on every pull request, with both lanes authenticating as different accounts. A pattern of account, date, artefact type and session identifier gives the nightly lane and the pull-request lane separate top-level prefixes for free, without either lane knowing the other exists, because the account name is the one fact the server already holds about the session. The alternative that teams reach for first — encoding the branch name into a custom per-session pattern — works, but it moves the layout decision into the test code, where it is edited by whoever is closest to a deadline. Keeping the stable axes in the server's pattern and using the per-session override only for genuine exceptions keeps the bucket navigable. ## What to take away - The key is what a prefix listing walks, so plan it before the first upload. - A session's own pattern beats the server's, which is the opposite of most settings. - The placeholder list is closed; anything else folds into the pattern you send. - Normalisation is part of the key, so reconstruct keys with the same rules or not at all.

  • Why can a per-session key pattern not reference your CI build number?
    Because the substitutions are a fixed list — the finished file, some of the session's capabilities, the account it authenticated as and the date of the upload — and none of them is a build number. The workable move is to put the value into the pattern string the session sends, since the session composes that string itself and can interpolate anything it knows before the request leaves the client.
  • Your tooling builds the expected key from the capabilities it sent and gets a miss. What is the likely cause?
    Normalisation. Most substituted values are lower-cased during expansion and every space in the finished key becomes a hyphen, so a browser name with a space or a capital letter produces a key that does not match a naive reconstruction. Either apply the same rules on the client side, or stop reconstructing keys and record the one the upload actually used.

saying these in an interview costs you the question

  • Thinks the capability renames the local file on the host
  • Assumes the server's pattern always wins over the session's
  • Expects placeholder values to keep their original spacing and case
  • Believes the placeholder list can be extended with facts of your own
  • Assumes any build of the server can push to object storage