When Browser Automation Selectors Break on Locale Switches
My automation browser profile leaked an id-ID locale, the UI rendered Tanya Qwen instead of Ask Qwen, and every exact-label selector quietly broke. Here is the two-layer fix.
TL;DR
An automation suite broke because the browser profile's id-ID locale made the target site render UI text in Indonesian, so exact-match selectors like "Ask Qwen" failed. The fix: regex alternation accepting both languages, plus pinning the locale via context emulation. A small assert-based test guards against regressions.
It happened during the evening run: my automation suite was processing the usual article pipeline when the send step suddenly died. The log said the textbox could not be found, followed by a false report that the session had been logged out. I was sure I had changed nothing in the code since the last clean run. My first guess was that the target site had shipped a big update that reworked its DOM structure or CSS classes.
I spent a good while manually inspecting elements: looking for overlooked selector changes, checking the network tab for unexpected redirects, even rerunning the script with longer timeouts. Nothing worked. Every clue pointed to a standard failure, but the root cause stayed hidden behind a layer of abstraction I had not suspected.
After opening the browser with headless mode off and watching the script run, I finally saw a different reality. The interface had not changed structurally at all. HTML attributes were intact, CSS classes were the same, but the on-screen text was different. Instead of the expected "Ask Qwen" or "Copy" buttons, the screen showed "Tanya Qwen" and "Salin".
It turned out the browser profile I used for automation was configured with the id-ID locale. The target site read the language preference from that profile and decided to render its interface in Indonesian. My old selectors only matched the English strings, so the send and copy steps failed outright. This was not a bug in the automation tool. It was the natural consequence of relying on static text inside a dynamic interface.
Why exact-match selectors break
This is where understanding how automation tools read the accessibility tree matters. When you take an aria snapshot, the result is not raw HTML but a YAML representation of the page's accessibility tree. The basic format looks like this: - role "name" [attribute=value] [3].
As an illustration, before the locale change the snapshot showed a line like - textbox "Ask Qwen" [e9]. After the browser profile switched to the Indonesian locale, the same line became - textbox "Tanya Qwen" [e9].
The name part is the element's accessible name, the text read by screen readers. The matching rules are strict. A string wrapped in plain quotes is matched as the exact value; for flexibility you use a regular expression wrapped in slashes, like /pattern/ [3].
Because my old selector did an exact match against "Ask Qwen", the system failed the moment the accessible name became "Tanya Qwen". Many developers assume that identical HTML structure means the text inside it stays the same forever. Modern applications frequently adjust language automatically based on signals from the browser itself. Relying on static text without considering a possible language change is a recipe for failures that are hard to diagnose, especially when automating third-party applications you do not control.
The fix: alternation patterns and locale emulation
I applied two layers of repair. I did not want the same incident to repeat just because a browser setting changed silently or the service shipped a minor update.
The first layer was changing the label-matching patterns to accept two languages at once using regex alternation. I widened the ask textbox pattern to (?:Ask|Tanya) Qwen. I used a non-capturing group (?:...) so the regex engine does not store unnecessary matches. The same treatment went to the other actions: (?:Copy|Salin) for the copy button and (?:Send|Kirim) for send. This way the script keeps finding the right element whether the interface renders in English or Indonesian.
# accept both UI locales: en-US "Ask Qwen" / id-ID "Tanya Qwen"
ref = find_ref(s, r'- textbox "(?:Ask|Tanya) Qwen"[^\[]*\[(e\d+)\]') or \
find_ref(s, r'- textbox "(?!Search)[^"]*"[^\[]*\[(e\d+)\]')The second layer attacked the source directly: the browser locale. The browser's interface language is determined by the navigator.language property, which follows the BCP 47 standard with values like en-US or id-ID [2].
The locale can be pinned through context emulation at browser initialization. For example, new_context(locale='de-DE') forces the browser to behave as if it were in a German-language environment [4]. Locking the locale at profile level removes the variable that can silently change UI text. Personally I prefer pinning the locale to one consistent language, usually English, for all production scripts. That keeps application behavior predictable and saves me from writing increasingly convoluted regexes covering five or six languages.
A self-check to prevent regressions
Changing the selectors alone does not provide long-term safety. I needed assurance that the new patterns genuinely work under both language conditions without running the entire heavy automation suite every time something small changes.
The solution is a tools/test_qwen_labels.py file: 12 assert statements that test the regex patterns against accessibility snapshots in two locales, with no external dependencies. The test checks pattern matches against the English snapshot, matches against the Indonesian snapshot, ensures the patterns do not match random text, and verifies the non-capturing groups do not interfere with results. Isolated browser contexts per automation session [1] keep the test deterministic, free of leftover state from earlier runs.
COPY_RE = r'- button "(?:Copy|Salin)"[^\[]*\[(e\d+)\]'
SNAP_ID = ('- textbox "Cari Obrolan" [e3]\n- textbox "Tanya Qwen" [e9]\n'
'- button "Kirim" [e13]\n- button "Salin" [e12]')
assert find_ref(SNAP_ID, COPY_RE) is None # copy button, not a textbox
assert 'textbox "Tanya Qwen"' in SNAP_ID # _ui_alive() stays aliveIf the target application changes suddenly, this local test fails first. An early warning before the production script truly breaks in the middle of the night when nobody watches the logs. With tolerant regex patterns combined with strict locale control, the automation script became far more stable and stopped panicking without a clear reason.
Sources
- [1] Playwright Documentation: Browser Contexts, accessed 22 September 2026.
- [2] MDN Web Docs: Navigator.language, accessed 22 September 2026.
- [3] Playwright Documentation: Aria Snapshots, accessed 22 September 2026.
- [4] Playwright Documentation: Emulation, accessed 22 September 2026.
- [5] Playwright Documentation: Locators, accessed 22 September 2026.