You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
k6 aims to be a versatile tool for both load and functional testing. However, it currently lacks intuitive and robust assertion capabilities needed for effective functional testing. Existing constructs like check(), fail(), and k6/execution.abort() have limitations:
check(): Records pass/fail metrics but does not affect the test’s pass/fail status or exit code unless thresholds are explicitly set. It also lacks native support for asynchronous code and provides limited context on failures.
fail(): Aborts the current iteration but does not mark the test as failed or exit with a non-zero exit code.
k6/execution.abort(): Immediately halts the test run but does not provide detailed failure context.
These limitations make it challenging to perform functional testing where immediate feedback and detailed failure information are crucial.
Objectives
Enable Assertions That Fail Tests: Allow users to write assertions that can mark a test as failed, optionally aborting it immediately, and cause k6 to exit with a non-zero exit code.
Provide Detailed Failure Context: Offer clear, human-readable error messages with details about what was expected, what was received, and where in the code the failure occurred.
Support Asynchronous Assertions: Allow assertions over asynchronous code, particularly important for browser testing and scenarios involving dynamic content.
Introduce Playwright-Compatible API: Facilitate a smooth transition for users familiar with Playwright by providing a compatible assertion library, especially beneficial for k6’s browser module.
Maintain Backward Compatibility: Avoid breaking existing scripts and workflows by introducing new capabilities in a way that does not disrupt current functionalities.
Suggested Solution (optional)
Note: following solution is dependent on #4062, and may also depend on #4065 depending on how much we rely on checks internally for soft expectations.
Add a new k6/testing package written in JavaScript/TypeScript, replicating the Playwright assertions API, to provide robust and intuitive assertion capabilities.
Expect API
Function: expect(value, [message])
Offers expressive expectations using matchers (e.g., toBe(), toContain(), toBeGreaterThan()).
1) Soft Expectation unmet.
Expected: otherValue to contain 'hello'.
Received: 'world'
File: script.js
Line: 15
32 | // some statement
33 >| expect(otherValue).toContain('hello');
2) Expectation unmet.
Expected: value to be greater than 10.
Received: 8
File: script.js
Line: 15
32 | // some statement
33 >| expect(value).toBeGreatherThan(10);
Error code 108
API
Here is the target proposed API, aligned with playwright. We might want to implement only a subset of matchers, depending on k6's specific needs.
Expectation
Description
expect(value: unknown).toBe()
Value is the same
expect(value: unknown).toBeCloseTo()
Number is approximately equal
expect(value: unknown).toBeDefined()
Value is not undefined
expect(value: unknown).toBeFalsy()
Value is falsy, e.g., false, 0, null, etc.
expect(value: unknown).toBeGreaterThan()
Number is more than
expect(value: unknown).toBeGreaterThanOrEqual()
Number is more than or equal
expect(value: unknown).toBeInstanceOf()
Object is an instance of a class
expect(value: unknown).toBeLessThan()
Number is less than
expect(value: unknown).toBeLessThanOrEqual()
Number is less than or equal
expect(value: unknown).toBeNaN()
Value is NaN
expect(value: unknown).toBeNull()
Value is null
expect(value: unknown).toBeTruthy()
Value is truthy, i.e., not false, 0, null, etc.
expect(value: unknown).toBeUndefined()
Value is undefined
expect(value: unknown).toContain()
String contains a substring
expect(value: unknown).toContain()
Array or set contains an element
expect(value: unknown).toContainEqual()
Array or set contains a similar element
expect(value: unknown).toEqual()
Value is similar—deep equality and pattern matching
expect(value: unknown).toHaveLength()
Array or string has length
expect(value: unknown).toHaveProperty()
Object has a property
expect(value: unknown).toMatch()
String matches a regular expression
expect(value: unknown).toMatchObject()
Object contains specified properties
expect(value: unknown).toStrictEqual()
Value is similar, including property types
expect(value: unknown).toThrow()
Function throws an error
expect(value: unknown).any()
Matches any instance of a class/primitive
expect(value: unknown).anything()
Matches anything
expect(value: unknown).arrayContaining()
Array contains specific elements
expect(value: unknown).closeTo()
Number is approximately equal
expect(value: unknown).objectContaining()
Object contains specific properties
expect(value: unknown).stringContaining()
String contains a substring
expect(value: unknown).stringMatching()
String matches a regular expression
Retrying (Async) Expectations
Designed for scenarios where the condition may not be immediately met (e.g., waiting for a DOM element). Use await with retry logic until the condition is met or a timeout is reached.
Soft Expectation unmet:
Locator: locator('input[name="login"]')
Expected string: "foo"
Received string: "test"
retry count: 9
timed out after: 5secs
32 | // We're expecting this to fail as we have typed 'test' into the input 33 >| await expect.soft(page.locator('input[name="login"]')).toHaveValue("foo");
By default, failed expectations will terminate test execution with a non-zero exit code. Our testing package also support soft assertions: failed soft assertions do not terminate test execution, but mark the test as failed. An expectation can be made soft using the .soft() helper:
// Test continues even if assertions failexpect.soft(response.status).toBe(200);expect.soft(data.items).toHaveLength(5);
Configuration options
Use expect.configure(options) to create a customized expect instance.
Options include timeout settings, default expectation type (hard or soft), and output formatting.
PS: internal stakeholders expressed the desire to be able to also configure the behavior of the expect function from outside the script, to apply it in a systematic way.
Ensure first-class integration
The k6/testing package should be an official part of k6, ensuring it feels like a first-class citizen. Regardless of where and how it is implemented, its import path should feel "native", as opposed to a jslib library which we don't judge desirable.
Users import it using the familiar k6/ prefix, maintaining consistency with other k6 modules.
import{expect}from'k6/testing';
Already existing or connected issues / PRs (optional)
Background
k6 aims to be a versatile tool for both load and functional testing. However, it currently lacks intuitive and robust assertion capabilities needed for effective functional testing. Existing constructs like
check()
,fail()
, andk6/execution.abort()
have limitations:check()
: Records pass/fail metrics but does not affect the test’s pass/fail status or exit code unless thresholds are explicitly set. It also lacks native support for asynchronous code and provides limited context on failures.fail()
: Aborts the current iteration but does not mark the test as failed or exit with a non-zero exit code.k6/execution.abort()
: Immediately halts the test run but does not provide detailed failure context.These limitations make it challenging to perform functional testing where immediate feedback and detailed failure information are crucial.
Objectives
Suggested Solution (optional)
Note: following solution is dependent on #4062, and may also depend on #4065 depending on how much we rely on checks internally for soft expectations.
Introduce
k6/testing
packageProof of concept
Add a new
k6/testing
package written in JavaScript/TypeScript, replicating the Playwright assertions API, to provide robust and intuitive assertion capabilities.Expect API
expect(value, [message])
toBe()
,toContain()
,toBeGreaterThan()
).Non-retrying expectations
Example
Target (for illustration) output
API
Here is the target proposed API, aligned with playwright. We might want to implement only a subset of matchers, depending on k6's specific needs.
expect(value: unknown).toBe()
expect(value: unknown).toBeCloseTo()
expect(value: unknown).toBeDefined()
expect(value: unknown).toBeFalsy()
expect(value: unknown).toBeGreaterThan()
expect(value: unknown).toBeGreaterThanOrEqual()
expect(value: unknown).toBeInstanceOf()
expect(value: unknown).toBeLessThan()
expect(value: unknown).toBeLessThanOrEqual()
expect(value: unknown).toBeNaN()
expect(value: unknown).toBeNull()
expect(value: unknown).toBeTruthy()
expect(value: unknown).toBeUndefined()
expect(value: unknown).toContain()
expect(value: unknown).toContain()
expect(value: unknown).toContainEqual()
expect(value: unknown).toEqual()
expect(value: unknown).toHaveLength()
expect(value: unknown).toHaveProperty()
expect(value: unknown).toMatch()
expect(value: unknown).toMatchObject()
expect(value: unknown).toStrictEqual()
expect(value: unknown).toThrow()
expect(value: unknown).any()
expect(value: unknown).anything()
expect(value: unknown).arrayContaining()
expect(value: unknown).closeTo()
expect(value: unknown).objectContaining()
expect(value: unknown).stringContaining()
expect(value: unknown).stringMatching()
Retrying (Async) Expectations
Designed for scenarios where the condition may not be immediately met (e.g., waiting for a DOM element). Use
await
with retry logic until the condition is met or a timeout is reached.Example
Target (for illustration) output
API
await expect(locator: browser.Locator).toBeAttached()
await expect(locator: browser.Locator).toBeChecked()
await expect(locator: browser.Locator).toBeDisabled()
await expect(locator: browser.Locator).toBeEditable()
await expect(locator: browser.Locator).toBeEmpty()
await expect(locator: browser.Locator).toBeEnabled()
await expect(locator: browser.Locator).toBeFocused()
await expect(locator: browser.Locator).toBeHidden()
await expect(locator: browser.Locator).toBeInViewport()
await expect(locator: browser.Locator).toBeVisible()
await expect(locator: browser.Locator).toContainText()
await expect(locator: browser.Locator).toHaveAccessibleDescription()
await expect(locator: browser.Locator).toHaveAccessibleName()
await expect(locator: browser.Locator).toHaveAttribute()
await expect(locator: browser.Locator).toHaveClass()
await expect(locator: browser.Locator).toHaveCount()
await expect(locator: browser.Locator).toHaveCSS()
await expect(locator: browser.Locator).toHaveId()
await expect(locator: browser.Locator).toHaveJSProperty()
await expect(locator: browser.Locator).toHaveRole()
await expect(locator: browser.Locator).toHaveScreenshot()
await expect(locator: browser.Locator).toHaveText()
await expect(locator: browser.Locator).toHaveValue()
await expect(locator: browser.Locator).toHaveValues()
Negation
Expectations, regardless of their retrying fashion can be negated using the
.not
helper:Soft assertions
By default, failed expectations will terminate test execution with a non-zero exit code. Our testing package also support soft assertions: failed soft assertions do not terminate test execution, but mark the test as failed. An expectation can be made soft using the
.soft()
helper:Configuration options
expect.configure(options)
to create a customizedexpect
instance.Example
PS: internal stakeholders expressed the desire to be able to also configure the behavior of the expect function from outside the script, to apply it in a systematic way.
Ensure first-class integration
k6/testing
package should be an official part of k6, ensuring it feels like a first-class citizen. Regardless of where and how it is implemented, its import path should feel "native", as opposed to a jslib library which we don't judge desirable.Already existing or connected issues / PRs (optional)
Direct
#4062
#4065
Indirect
#3406
The text was updated successfully, but these errors were encountered: