k6 v1.3.0 is here 🎉! This release includes:
- Browser module gets:
locator.locator
,locator.contentFrame
, andFrameLocator.locator
for powerful locator chaining and iframe handling.locator|frame|FrameLocator.getBy*
for targeting elements without relying on brittle CSS selectors.locator.filter
for filtering locators for more precise element targeting.locator.boundingBox
for retrieving element geometry.page.waitForResponse
for waiting on specific HTTP responses.
Deprecations
A new summary mode disabled
has been introduced to replace the "no summary" option #5118
The --no-summary
flag and its corresponding environment variable K6_NO_SUMMARY
have been deprecated in favor of
the new disabled
summary mode. This change unifies the configuration experience for controlling the end-of-test summary.
You can now disable the end-of-test summary with either --summary-mode=disabled
or K6_SUMMARY_MODE=disabled
.
The legacy
summary mode has been deprecated #5138
The legacy
summary mode was introduced in k6 v1.0, when the end-of-test summary was revamped with the addition of two
new modes: compact
and full
.
Its purpose was to ease the transition for users who relied heavily on the old summary format.
However, we’ve now reached the point where it’s time to deprecate it.
The plan is to fully remove it in k6 v2.0, so please migrate to either compact
or full
to ensure readiness for the
next major release.
New features
locator.locator
#5073
The locator.locator
method allows you to define locators relative to a parent locator, enabling powerful locator chaining and nesting. This feature lets you create more precise element targeting by combining multiple selectors in a hierarchical manner.
await page
.locator('[data-testid="inventory"]')
.locator('[data-item="apples"]')
.locator('button.add')
.click();
This nesting capability provides a more intuitive way to navigate complex DOM structures and serves as the foundation for other locator
APIs in this release that require such hierarchical targeting.
locator.contentFrame
#5075
The browser module now supports locator.contentFrame()
, which returns a new type frameLocator
. This method is essential for switching context from the parent page to iframe contents.
frameLocator
types target iframe elements on the page and provide a gateway to interact with their contents. Unlike regular locator
s that work within the current frame
context, frameLocator
s specifically target iframe elements and prepare them for content interaction.
This approach is essential for iframe interaction because:
- Iframes create separate DOM contexts that require special handling.
- Browsers enforce security boundaries between frames.
- Iframe content may load asynchronously and needs proper waiting.
- Using
elementHandle
for iframe interactions is error-prone and can lead to stale references, whileframeLocator
provide reliable, auto-retrying approaches.
Example usage:
// Get iframe element and switch to its content frame
const iframeLocator = page.locator('iframe[name="payment-form"]');
const frame = await iframeLocator.contentFrame();
frameLocator.locator
#5075
We've also added frameLocator.locator
which allows you to create locator
s for elements inside an iframe. Once you've targeted an iframe with page.contentFrame()
, you can use .locator()
to find and interact with elements within that iframe's content with the frameLocator
type.
Example usage:
// Target an iframe and interact with elements inside it
const iframe = page.locator('iframe[name="checkout-frame"]').contentFrame();
await iframe.locator('input[name="card-number"]').fill('4111111111111111');
await iframe.locator('button[type="submit"]').click();
This functionality enables testing of complex web applications that use iframes for embedded content, payment processing, authentication widgets, and third-party integrations.
locator.boundingBox
#5076
The browser module now supports locator.boundingBox()
, which returns the bounding box of an element as a rectangle with position and size information. This method provides essential geometric data about elements on the page, making it valuable for visual testing, and layout verification.
Using locator.boundingBox()
is recommended over elementHandle.boundingBox()
because locators have built-in auto-waiting and retry logic, making them more resilient to dynamic content and DOM changes. While element handles can become stale if the page updates, locators represent a live query that gets re-evaluated, ensuring more reliable test execution.
The method returns a rectangle object with x
, y
, width
, and height
properties, or null
if the element is not visible:
// Get bounding box of an element
const submitButton = page.locator('button[type="submit"]');
const rect = await submitButton.boundingBox();
Locator filtering #5114, #5150
The browser module now supports filtering options for locators, allowing you to create more precise and reliable element selections. This enhancement improves the robustness of your tests by enabling you to target elements that contain or exclude specific text, reducing reliance on brittle CSS selectors.
locator.filter()
creates a new locator
that matches only elements containing or excluding specified text.
// Filter list items that contain specific text
const product2Item = page
.locator('li')
.filter({ hasText: 'Product 2' });
// Filter items that do NOT contain specific text using regex
const otherProducts = page
.locator('li')
.filter({ hasNotText: /Product 2/ });
It's also possible to filter locators during their creation with options.
page.locator(selector, options)
creates page locators with optional text filtering:
// Create locators with text filtering during creation
const submitButton = page.locator('button', { hasText: 'Submit Order' });
await submitButton.click();
frame.locator(selector, options)
creates frame locators with optional text filtering:
// Filter elements within frame context
const frame = page.mainFrame();
const input = frame.locator('input', { hasNotText: 'Disabled' });
locator.locator(selector, options)
chains locators with optional text filtering:
// Chain locators with filtering options
await page
.locator('[data-testid="inventory"]')
.locator('[data-item="apples"]', { hasText: 'Green' })
.click();
frameLocator.locator(selector, options)
create locators within iframe content with optional text filtering:
// Filter elements within iframe content
const iframe = page.locator('iframe').contentFrame();
await iframe.locator('button', { hasText: 'Submit Payment' }).click();
frame.getBy*
, locator.getBy*
, frameLocator.getBy*
#5105, #5106, #5135
The browser module now supports all getBy*
methods on frame
, locator
, and frameLocator
types, expanding on the page.getBy*
APIs introduced in v1.2.1. This enhancement provides consistent element targeting across all browser automation contexts, improving Playwright compatibility and offering more flexible testing workflows. The available methods on all types are:
getByRole()
- Find elements by ARIA rolegetByText()
- Find elements by text contentgetByLabel()
- Find elements by associated label textgetByPlaceholder()
- Find elements by placeholder textgetByAltText()
- Find elements by alt textgetByTitle()
- Find elements by title attributegetByTestId()
- Find elements by data-testid attribute
Examples across different types
// Frame context
const frame = page.mainFrame();
await frame.getByRole('button', { name: 'Submit' }).click();
await frame.getByLabel('Email').fill('user@example.com');
// Locator context (for scoped searches)
const form = page.locator('form.checkout');
await form.getByRole('textbox', { name: 'Card number' }).fill('4111111111111111');
await form.getByTestId('submit-button').click();
// FrameLocator context (for iframe content)
const paymentFrame = page.locator('iframe').contentFrame();
await paymentFrame.getByLabel('Cardholder name').fill('John Doe');
await paymentFrame.getByRole('button', { name: 'Pay now' }).click();
// Chaining for precise targeting
await page
.locator('.product-list')
.getByText('Premium Plan')
.getByRole('button', { name: 'Select' })
.click();
This expansion makes k6 browser automation more versatile and aligns with modern testing practices where element targeting by semantic attributes (roles, labels, text) is preferred over fragile CSS and XPath selectors.
page.waitForResponse
#5002
The browser module now supports page.waitForResponse()
, which allows you to wait for HTTP responses that match specific URL patterns during browser automation. This method is particularly valuable for testing scenarios where you need to ensure specific network requests complete before proceeding with test actions.
The method supports multiple URL pattern matching strategies:
// Wait for exact URL match
await page.waitForResponse('https://api.example.com/data');
// Wait for regex pattern match
await page.waitForResponse(/\/api\/.*\.json$/);
// Use with Promise.all for coordinated actions
await Promise.all([
page.waitForResponse('https://api.example.com/user-data'),
page.click('button[data-testid="load-user-data"]')
]);
This complements the existing waitForURL
method by focusing on HTTP responses rather than navigation events, providing more granular control over network-dependent test scenarios.
Thank you, @HasithDeAlwis, for contributing this feature.
UX improvements and enhancements
- #5117 Unifies unauthenticated errors for Cloud commands.
- #5125 Changes a warn log to a debug when a worker type is used on a website under test.
- #5111 Adds retries to actionability based APIs (
locator
) when elements aren't visible. - #5004 Removes undefined headers from
route.continue
/fulfill
. - #4984 Adds link to documentation in
k6 --help
output. Thank you, @Nishant891 for the change.
Bug fixes
- #5079 Fixes version of k6 when it is built with xk6.
- #5057 Fixes a panic on the deprecated
k6 login cloud
command. Thanks @indygriffiths for reporting it! - #5059 Fixes group order in end of test summary when scenarios are used.
- #5081 Fixes auto extension resolution only working if binary is called
k6
after a fix in v1.2.2. - #5089 Fixes gRPC calls not using loaded types and erroring out, especially around the usage of
Any
. - #5071, #5086, #5163 Fixes
click
action in browser module when working iniframe
s and CORS. - #5084 Fixes a browser module issue when adopting elements from
util
tomain
execution contexts in Chromium. - #5178 Fixes a subtle metric labelling issue in Prometheus RW output.
- #5200 Fixes a bug where clearTimeout would not recalculate the timer but instead will run the next timer earlier if it used to remove the earliest one. Thanks to @kyriog 🙇.
Maintenance and internal improvements
- #5165 Fixes arguments order for multiple
{require|assert}.{Equal|NotEqual}
and equivalent calls. - #5157 Fixes the test
TestURLSkipRequest
for Chrome 140+. - #5074, #5078 Uses common.IsNullish through the code instead of other variants of it or custom helpers.
- #5072, #5107, #5108 Update k6packager debian to latest LTS and fixes due to the update.
- #5051, #5052, #5053 Adds tests and refactors
getBy*
andwaitForURL
implementations. - #5101 Updates times to nanoseconds to make tests less flakey in CI.
- #5122 Migrates to use a new code signing process for Windows binaries instead of using the static code-signing certificate. Thanks @martincostello for the contribution!
- #5048 Updates release issue template after v1.2.0
- #5046 Adds architecture overview and code authoring instructions for Claude Code and alike.
Roadmap
Deprecation of First Input Delay (FID) Web Vital
Following the official web vitals guidance, First Input Delay (FID) is no longer a Core Web Vital as of September 9, 2024, having been replaced by Interaction to Next Paint (INP). The k6 browser module already emits INP metrics, and we're planning to deprecate FID support to align with industry standards.
FID only measures the delay before the browser runs your event handler, so it ignores the time your code takes and the delay to paint the UI—often underestimating how slow an interaction feels. INP captures the full interaction latency (input delay + processing + next paint) across a page’s interactions, so it better reflects real user-perceived responsiveness and is replacing FID.
Planned timeline
- v1.4.x+: Deprecation warnings will appear in the terminal when FID metrics are used #5179.
- Grafana Cloud k6: Similar deprecation warnings will be shown in the cloud platform.
- v2.0: Complete removal of FID metric support.
Action required
If you're currently using FID in your test scripts for thresholds or relying on it in external integrations, you should migrate to using INP as soon as possible.
// Instead of relying on FID
export const options = {
thresholds: {
// 'browser_web_vital_fid': ['p(95)<100'], // Deprecated
'browser_web_vital_inp': ['p(95)<200'], // Use INP instead
},
};
This change ensures k6 browser testing stays aligned with modern web performance best practices and Core Web Vitals standards.
OpenTelemetry stabilization
We aim to stabilize OpenTelemetry's experimental metric output, promoting vendor neutrality for metric outputs. OpenTelemetry is becoming the standard protocol for metric format in observability. Our goal is to enable k6 users to utilize their preferred metric backend storage without any technological imposition.