Chrome and Node disagree about this URL

It cost me the number one result twice, and no test could have caught it — because the test suite runs on Node, where the bug does not exist.


I was parsing Google result pages in a Chrome extension, pulling the domain out of each citation. The citation text is not a clean URL — it arrives with a separator and a date glued on, like a breadcrumb someone forgot to trim.

So I did the obvious thing and handed it to the platform.

new URL('https://university.webflow.com · Sep 26, 2025').hostname

Two answers, depending on where you run it:

Node    →  university.webflow.com
Chrome  →  university.webflow.xn--com%20%20sep%2026,%202025-bgb

Same standard, same method, two results. Chrome runs the trailing text through IDNA punycoding and produces something that is technically a hostname and semantically garbage. Node stops at the space.

What it actually cost

The extension recorded which domains a search cited. When the hostname came back as that punycoded string, it did not match any domain in the results underneath, so the citation was recorded as pointing somewhere that did not rank.

That is not a cosmetic bug. The entire study this extension exists to run measures whether cited sources also rank. A parser that silently invents non-matching hostnames does not produce noisy data — it produces data that is wrong in exactly the direction the study is looking.

It took the top result off two different searches before I caught it.

Why the tests were useless

The parser had coverage. Every test passed, on every run, the whole time the bug was live.

They ran under Node.

The code ships to Chrome. Node is the one environment where that input parses correctly, and Chrome is the one where it does not — so the suite was exhaustively verifying behaviour in the only place the bug could not happen.

This is the part worth keeping. A test suite proves behaviour in the runtime it runs in. Usually that is close enough to the runtime that ships. When it is not — when the two disagree about a platform primitive — coverage tells you nothing, and it tells you nothing confidently, with a green check next to it.

I do not think more tests would have helped. A test written by the same person with the same wrong assumption runs in the same wrong place.

The fix

Stop asking the platform.

const candidate = citeText
  .replace(/^\s*https?:\/\//i, '')
  .trim()
  .split(/[\s›»·|,]+/)[0]
  .replace(/[/:].*$/, '')
  .replace(/^www\./i, '')
  .toLowerCase();

return HOSTNAME.test(candidate) ? candidate : '';

Strip the scheme, split on the first separator, cut at a slash or a colon, drop www., lowercase it — then validate that what came out actually looks like a hostname, and return nothing if it does not.

It is duller than new URL(). It is also the same in every JavaScript runtime that will ever execute it, which is the only property I actually needed.

What I would do differently

Not “write more tests”. The lesson is narrower and more useful than that.

When code depends on a platform primitive — URL parsing, date parsing, Intl, text encoding — and the test runtime differs from the ship runtime, that dependency needs verifying in the runtime that ships, or removing. Most of the time the difference does not matter. When it does, nothing in your normal process will tell you.

The way I actually found it was not a test. It was re-parsing a stored copy of the page and comparing the result against what the extension had recorded live. The two disagreed. Every real bug in this parser was found that way, which is the strongest argument I have for keeping raw captures instead of just the numbers you computed from them.