Skip to content

v27.0.0

Choose a tag to compare

@soulgalore soulgalore released this 01 May 17:37
· 141 commits to main since this release

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 optional id:/xpath:/text:/link:/name:/class: prefixes. The old AndWait family 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 full cookie command.
  • 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.elementWait from 0 to 6000ms. All element commands (click, addText) now auto-wait up to 6 seconds for elements to appear. Set --timeouts.elementWait 0 to 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, use commands.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'), and commands.set('#field', 'value') directly. Supports prefixes: id:, xpath:, text:, link:, name:, class: — CSS selector is the default #2390 #2408.
  • The AndWait methods 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 for addText with 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.elementWait option 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), and commands.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.cookie for managing browser cookies: get, getAll, set, delete, deleteAll #2420.
  • Added commands.getAttribute(selector, name), commands.isEnabled(selector), and commands.isChecked(selector) for element state queries #2421.
  • Added commands.hover(selector) as alias for mouse.moveTo, commands.press(key) for keyboard input, commands.getTitle() and commands.getUrl() for page info #2422.
  • Added commands.check(selector) and commands.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 stops iwdp automatically and exits with a clear error if it isn't installed (brew install ios-webkit-debug-proxy). New --safari.includeResponseBodies CLI 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.browser in the result JSON, not just what the user passed via --chrome.args #2447.
  • Record Firefox preferences and the resolved binary path under info.browser in the result JSON #2448.
  • Write long-task ranges and user-timing marks/measures into the HAR under pages[].pageTimings as _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.targetCreated event could leave the HAR empty #2440.
  • Fixed drawtext failing 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() after perform() in all mouse commands to prevent pointer/key state leaking between actions #2406.
  • Standardized error handling in all command run() methods using executeCommand with 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 --viewPort is 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_index now returns (None, None) on internal errors instead of letting the exception propagate up through calculate_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 losing keep pts: lines and therefore filmstrip frames — and could deadlock when ffmpeg's debug output filled the OS pipe buffer #2443.
  • Converted visualmetrics-portable.py path handling to pathlib for consistent cross-platform behaviour #2442.