v27.0.0
Highlights
This release is a big push on the scripting API and on iOS real-device testing. All old scripting commands will work but we have new and improveed
syntax that you can use if you follow our documentation. This release also tries to support the upcoming Chrome soft navigations.
- Unified selector syntax across commands. All interaction commands now accept selectors directly (
commands.click('#btn'),commands.addText('#input', 'text'), etc.) with optionalid:/xpath:/text:/link:/name:/class:prefixes. The oldAndWaitfamily is deprecated in favour of{ waitForNavigation: true }. - A much bigger convenience-command surface. New
type,find,getText,getValue,isVisible,exists,clear,fill,getAttribute,isEnabled,isChecked,hover,press,getTitle,getUrl,check,uncheck,scrollIntoView,select.byText,waitForUrl,clickAndMeasure,click.byText, plus a fullcookiecommand. - Chrome soft navigations. SPA route changes (React, Next.js, Vue, Turbo) now produce a HAR page and the standard FCP/LCP/CLS/INP metrics, using Chrome's PerformanceObserver soft-navigation entry type.
- Safari on iOS over USB. HAR via
ios_webkit_debug_proxy, plus video recording and visual metrics via a CoreMediaIO screen-capture helper. - TypeScript navigation scripts are now supported on Node.js 22.18.0+ via native type stripping.
Breaking
- Changed default
--timeouts.elementWaitfrom 0 to 6000ms. All element commands (click, addText) now auto-wait up to 6 seconds for elements to appear. Set--timeouts.elementWait 0to restore the old behavior #2389. - Click commands now use the Selenium Actions API for real OS-level mouse events instead of JavaScript
.click(). Elements must be visible and interactable. If you need to click a hidden element, usecommands.js.run()instead #2381.
Added
- Unified selector syntax for all interaction commands. You can now call
commands.click('#btn'),commands.addText('#input', 'text'),commands.wait('#el', { timeout: 5000 }),commands.select('#dropdown', 'value'), andcommands.set('#field', 'value')directly. Supports prefixes:id:,xpath:,text:,link:,name:,class:— CSS selector is the default #2390 #2408. - The
AndWaitmethods are deprecated in favour of{ waitForNavigation: true }#2390 #2396. - Navigation methods (back, forward, refresh) now support
{ waitForNavigation: true }for consistency #2408. - Added
commands.type(selector, text)as a shorthand foraddTextwith a more conventional parameter order #2387. - Added
commands.find(selector, options)helper that combines element lookup with optional waiting and visibility checks #2386. - Added
commands.measure.clickAndMeasure(alias, selector)shortcut for the common measure.start/click/measure.stop pattern #2382. - Added
commands.click.byText(text)to click any element by visible text, not just links #2384. - Added
--timeouts.elementWaitoption for auto-waiting on elements before interaction #2383. - Added support for TypeScript scripts (.ts, .mts, .cts) using Node.js native type stripping (requires Node.js 22.18.0+) #2363.
- Added
commands.getText(selector),commands.getValue(selector), andcommands.isVisible(selector)convenience methods #2411. - Added
commands.exists(selector, { timeout })for checking element presence without throwing #2412. - Added
commands.clear(selector)to clear form element content #2413. - Added
commands.fill({ selector: text, ... })for filling multiple form fields at once #2414. - Added unified selector syntax to mouse commands:
commands.mouse.singleClick(selector),commands.mouse.doubleClick(selector),commands.mouse.contextClick(selector),commands.mouse.moveTo(selector)#2415. - Added
commands.cookiefor managing browser cookies:get,getAll,set,delete,deleteAll#2420. - Added
commands.getAttribute(selector, name),commands.isEnabled(selector), andcommands.isChecked(selector)for element state queries #2421. - Added
commands.hover(selector)as alias for mouse.moveTo,commands.press(key)for keyboard input,commands.getTitle()andcommands.getUrl()for page info #2422. - Added
commands.check(selector)andcommands.uncheck(selector)for checkbox/radio interactions #2423. - Added
commands.scrollIntoView(selector)to scroll elements into the viewport #2425. - Added
commands.select.byText(selector, text)to select dropdown options by visible text #2426. - Added
commands.waitForUrl(pattern, { timeout })to wait for URL changes after navigation #2427. - Added video recording and visual metrics for Safari on iOS over USB. A native helper captures the device's screen via CoreMediaIO and pipes frames to ffmpeg. The device is woken into screen-capture mode automatically — no manual QuickTime step required #2432 #2436.
- Added HAR capture for Safari on iOS via
ios_webkit_debug_proxy. Browsertime starts and stopsiwdpautomatically and exits with a clear error if it isn't installed (brew install ios-webkit-debug-proxy). New--safari.includeResponseBodiesCLI option. macOS Safari is unaffected #2431. - Added soft navigation support for Chrome. Single-page app route changes (React, Next.js, Vue, Turbo) now produce a HAR page and the standard FCP/LCP/CLS/INP metrics, using Chrome's PerformanceObserver soft-navigation entry type to confirm user interaction + URL change + visible paint #2438.
- Updated dependencies to latest compatible versions: chrome-har 1.2.1, selenium-webdriver 4.43.0, execa 9.6.1, jimp 1.6.1 #2437.
- Firefox 149 in the Docker container #2375.
- Record the full Chrome/Edge configuration browsertime applied to the browser (args, preferences, mobile emulation, binary path, extensions count) under
info.browserin the result JSON, not just what the user passed via--chrome.args#2447. - Record Firefox preferences and the resolved binary path under
info.browserin the result JSON #2448. - Write long-task ranges and user-timing marks/measures into the HAR under
pages[].pageTimingsas_longTasks(array of[startMs, endMs]tuples) and_userTimings(object keyed by entry name → startTime), so downstream waterfall viewers can show what the browser was doing between requests #2449. - Added modern device profiles to the mobile emulation device list #2446.
Fixed
- Pin the iOS Safari tab being measured so unrelated open tabs cannot clobber the active inspector target. The WebDriver tab is navigated to a sentinel URL and identified by it before iWDP messages are processed, otherwise an unrelated
Target.targetCreatedevent could leave the HAR empty #2440. - Fixed
drawtextfailing on ffmpeg 8 by quoting font paths and forwarding ffmpeg stderr so font/filter errors are visible #2435. - Fixed Firefox network idle timeout on repeated iterations. The WebSocket message listener was never removed between iterations, causing the inflight counter to go negative #2391.
- Fixed Firefox network idle timeout when no network events arrive. Timestamps were initialized as undefined causing the idle check to never trigger #2394.
- Fixed intermittent crash when setting orange background before document.body exists on Edge/Windows #2393.
- Element interaction error messages now include the current page URL for easier debugging #2385.
- Added
actions.clear()afterperform()in all mouse commands to prevent pointer/key state leaking between actions #2406. - Standardized error handling in all command
run()methods usingexecuteCommandwith URL context. Fixed typo in select.deselectById and removed dead code in wait #2410. - Added clear error message when loading TypeScript scripts on Node.js older than 22.18.0 #2404.
- Disabled the "You can zoom in and out" popup on Android #2378.
- Warn when
--viewPortis set with a width below 500px on desktop Chrome. Chrome enforces a hard ~500px minimum window width and silently clamps narrower requests, but browsertime kept recording the filmstrip and video at the requested size, leaving captures cropped. The warning points users at mobile emulation, which lays out at the requested size while the OS window stays desktop-sized #2445. - Stop a perceptual SSIM failure from discarding every other visual metric for the iteration.
calculate_perceptual_speed_indexnow returns(None, None)on internal errors instead of letting the exception propagate up throughcalculate_visual_metrics, so SpeedIndex, FCP, LCP and the rest still flow through when SSIM can't run #2444. - Drain ffmpeg/ffprobe stderr after the process exits in
visualmetrics-portable.py. The previous Popen+poll loop dropped output buffered between the last read and exit — silently losingkeep pts:lines and therefore filmstrip frames — and could deadlock when ffmpeg's debug output filled the OS pipe buffer #2443. - Converted
visualmetrics-portable.pypath handling topathlibfor consistent cross-platform behaviour #2442.