diff --git a/docs/sources/.eslintrc.js b/docs/sources/.eslintrc.js
index a5ab855e0..332400d63 100644
--- a/docs/sources/.eslintrc.js
+++ b/docs/sources/.eslintrc.js
@@ -40,5 +40,6 @@ module.exports = {
__ENV: 'readonly',
__ITER: 'readonly',
open: 'readonly',
+ window: 'readonly',
},
};
diff --git a/docs/sources/v0.50.x/_index.md b/docs/sources/v0.50.x/_index.md
new file mode 100644
index 000000000..77c60159d
--- /dev/null
+++ b/docs/sources/v0.50.x/_index.md
@@ -0,0 +1,105 @@
+---
+aliases:
+ - /docs/k6/
+description: 'The k6 documentation covers everything you need to know about k6 OSS, load testing, and performance testing.'
+menuTitle: Grafana k6
+title: Grafana k6 documentation
+weight: -10
+---
+
+# Grafana k6 documentation
+
+This documentation will help you go from a total beginner to a seasoned k6 expert!
+
+## Get started
+
+
+
+## What is k6?
+
+Grafana k6 is an open-source load testing tool that makes performance testing easy and productive for engineering teams.
+k6 is free, developer-centric, and extensible.
+
+Using k6, you can test the reliability and performance of your systems and catch performance regressions and problems earlier.
+k6 will help you to build resilient and performant applications that scale.
+
+k6 is developed by [Grafana Labs](https://grafana.com/) and the community.
+
+Watch the video below to learn more about k6 and why it could be the missing puzzle in your Grafana stack.
+
+{{< youtube id="1mtYVDA2_iQ" >}}
+
+## Key features
+
+k6 is packed with features, which you can learn all about in the documentation.
+Key features include:
+
+- [CLI tool](https://grafana.com/docs/k6//using-k6/k6-options/how-to) with developer-friendly APIs.
+- Scripting in JavaScript ES2015/ES6 - with support for [local and remote modules](https://grafana.com/docs/k6//using-k6/modules)
+- [Checks](https://grafana.com/docs/k6//using-k6/checks) and [Thresholds](https://grafana.com/docs/k6//using-k6/thresholds) - for goal-oriented, automation-friendly load testing
+
+## Use cases
+
+k6 users are typically Developers, QA Engineers, SDETs, and SREs.
+They use k6 for testing the performance and reliability of APIs, microservices, and websites.
+Common k6 use cases are:
+
+- **Load testing**
+
+ k6 is optimized for minimal resource consumption and designed for running high load tests
+ ([spike](https://grafana.com/docs/k6//testing-guides/test-types/spike-testing), [stress](https://grafana.com/docs/k6//testing-guides/test-types/stress-testing), [soak tests](https://grafana.com/docs/k6//testing-guides/test-types/soak-testing)).
+
+- **Browser testing**
+
+ Through [k6 browser](https://grafana.com/docs/k6//using-k6-browser), you can run browser-based performance testing and catch issues related to browsers only which can be skipped entirely from the protocol level.
+
+- **Chaos and resilience testing**
+
+ You can use k6 to simulate traffic as part of your chaos experiments, trigger them from your k6 tests or inject different types of faults in Kubernetes with [xk6-disruptor](https://grafana.com/docs/k6//javascript-api/xk6-disruptor).
+
+- **Performance and synthetic monitoring**
+
+ With k6, you can automate and schedule to trigger tests very frequently with a small load to continuously validate the performance and availability of your production environment.
+
+## Load Testing Manifesto
+
+Our load testing manifesto is the result of having spent years hip deep in the trenches, doing performance- and load testing.
+We’ve created it to be used as guidance, helping you in getting your performance testing on the right track!
+
+- [Simple testing is better than no testing](https://k6.io/our-beliefs/#simple-testing-is-better-than-no-testing)
+- [Load testing should be goal oriented](https://k6.io/our-beliefs/#load-testing-should-be-goal-oriented)
+- [Load testing by developers](https://k6.io/our-beliefs/#load-testing-by-developers)
+- [Developer experience is super important](https://k6.io/our-beliefs/#developer-experience-is-super-important)
+- [Load test in a pre-production environment](https://k6.io/our-beliefs/#load-test-in-a-pre-production-environment)
+
+## What k6 does not
+
+k6 is a high-performing load testing tool, scriptable in JavaScript. The architectural design to have these capabilities brings some trade-offs:
+
+- **Does not run natively in a browser**
+
+ By default, k6 does not render web pages the same way a browser does.
+ Browsers can consume significant system resources.
+ Skipping the browser allows running more load within a single machine.
+
+ However, with [k6 browser](https://grafana.com/docs/k6//using-k6-browser), you can interact with real browsers and collect frontend metrics as part of your k6 tests.
+
+- **Does not run in NodeJS**
+
+ JavaScript is not generally well suited for high performance.
+ To achieve maximum performance, the tool itself is written in Go, embedding a JavaScript runtime allowing for easy test scripting.
+
+ If you want to import npm modules or libraries using NodeJS APIs, you can [bundle npm modules with webpack](https://grafana.com/docs/k6//using-k6/modules#bundling-node-modules) and import them in your tests.
diff --git a/docs/sources/v0.50.x/examples/_index.md b/docs/sources/v0.50.x/examples/_index.md
new file mode 100644
index 000000000..3b126c0c8
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/_index.md
@@ -0,0 +1,10 @@
+---
+title: Examples
+weight: 11
+---
+
+# Examples
+
+
+
+{{< section >}}
diff --git a/docs/sources/v0.50.x/examples/api-crud-operations.md b/docs/sources/v0.50.x/examples/api-crud-operations.md
new file mode 100644
index 000000000..2339ad529
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/api-crud-operations.md
@@ -0,0 +1,261 @@
+---
+title: 'API CRUD Operations'
+description: 'This example covers the usage of k6 to test a REST API CRUD operations.'
+weight: 10
+---
+
+# API CRUD Operations
+
+The examples showcase the testing of CRUD operations on a REST API.
+
+CRUD refers to the basic operations in a database: Create, Read, Update, and Delete. We can map these operations to HTTP methods in REST APIs:
+
+- _Create_: HTTP `POST` operation to create a new resource.
+- _Read_: HTTP `GET` to retrieve a resource.
+- _Update_: HTTP `PUT`or `PATCH` to change an existing resource.
+- _Delete_: HTTP `DELETE` to remove a resource.
+
+This document has two examples, one that uses the core k6 APIs (`k6/http` and `checks`) and another to show the more recent APIs [`httpx`](https://grafana.com/docs/k6//javascript-api/jslib/httpx) and [`k6chaijs`](https://grafana.com/docs/k6//javascript-api/jslib/k6chaijs)).
+
+## Test steps
+
+In the [setup() stage](https://grafana.com/docs/k6//using-k6/test-lifecycle#setup-and-teardown-stages) we create a user for the [k6 HTTP REST API](https://test-api.k6.io/). We then retrieve and return a bearer token to authenticate the next CRUD requests.
+
+The steps implemented in the [VU stage](https://grafana.com/docs/k6//using-k6/test-lifecycle#the-vu-stage) are as follows:
+
+1. _Create_ a new resource, a "croc".
+2. _Read_ the list of "crocs".
+3. _Update_ the name of the "croc" and _read_ the "croc" to confirm the update operation.
+4. _Delete_ the "croc" resource.
+
+## Core k6 APIs example
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check, group, fail } from 'k6';
+
+export const options = {
+ vus: 1,
+ iterations: 1,
+};
+
+// Create a random string of given length
+function randomString(length, charset = '') {
+ if (!charset) charset = 'abcdefghijklmnopqrstuvwxyz';
+ let res = '';
+ while (length--) res += charset[(Math.random() * charset.length) | 0];
+ return res;
+}
+
+const USERNAME = `${randomString(10)}@example.com`; // Set your own email or `${randomString(10)}@example.com`;
+const PASSWORD = 'superCroc2019';
+
+const BASE_URL = 'https://test-api.k6.io';
+
+// Register a new user and retrieve authentication token for subsequent API requests
+export function setup() {
+ const res = http.post(`${BASE_URL}/user/register/`, {
+ first_name: 'Crocodile',
+ last_name: 'Owner',
+ username: USERNAME,
+ password: PASSWORD,
+ });
+
+ check(res, { 'created user': (r) => r.status === 201 });
+
+ const loginRes = http.post(`${BASE_URL}/auth/token/login/`, {
+ username: USERNAME,
+ password: PASSWORD,
+ });
+
+ const authToken = loginRes.json('access');
+ check(authToken, { 'logged in successfully': () => authToken !== '' });
+
+ return authToken;
+}
+
+export default (authToken) => {
+ // set the authorization header on the session for the subsequent requests
+ const requestConfigWithTag = (tag) => ({
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ tags: Object.assign(
+ {},
+ {
+ name: 'PrivateCrocs',
+ },
+ tag
+ ),
+ });
+
+ let URL = `${BASE_URL}/my/crocodiles/`;
+
+ group('01. Create a new crocodile', () => {
+ const payload = {
+ name: `Name ${randomString(10)}`,
+ sex: 'F',
+ date_of_birth: '2023-05-11',
+ };
+
+ const res = http.post(URL, payload, requestConfigWithTag({ name: 'Create' }));
+
+ if (check(res, { 'Croc created correctly': (r) => r.status === 201 })) {
+ URL = `${URL}${res.json('id')}/`;
+ } else {
+ console.log(`Unable to create a Croc ${res.status} ${res.body}`);
+ return;
+ }
+ });
+
+ group('02. Fetch private crocs', () => {
+ const res = http.get(`${BASE_URL}/my/crocodiles/`, requestConfigWithTag({ name: 'Fetch' }));
+ check(res, { 'retrieved crocs status': (r) => r.status === 200 });
+ check(res.json(), { 'retrieved crocs list': (r) => r.length > 0 });
+ });
+
+ group('03. Update the croc', () => {
+ const payload = { name: 'New name' };
+ const res = http.patch(URL, payload, requestConfigWithTag({ name: 'Update' }));
+ const isSuccessfulUpdate = check(res, {
+ 'Update worked': () => res.status === 200,
+ 'Updated name is correct': () => res.json('name') === 'New name',
+ });
+
+ if (!isSuccessfulUpdate) {
+ console.log(`Unable to update the croc ${res.status} ${res.body}`);
+ return;
+ }
+ });
+
+ group('04. Delete the croc', () => {
+ const delRes = http.del(URL, null, requestConfigWithTag({ name: 'Delete' }));
+
+ const isSuccessfulDelete = check(null, {
+ 'Croc was deleted correctly': () => delRes.status === 204,
+ });
+
+ if (!isSuccessfulDelete) {
+ console.log(`Croc was not deleted properly`);
+ return;
+ }
+ });
+};
+```
+
+{{< /code >}}
+
+## httpx and k6chaijs example
+
+{{< code >}}
+
+```javascript
+import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js';
+import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js';
+import {
+ randomIntBetween,
+ randomItem,
+ randomString,
+} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
+
+export const options = {
+ // for the example, let's run only 1 VU with 1 iteration
+ vus: 1,
+ iterations: 1,
+};
+
+const USERNAME = `user${randomIntBetween(1, 100000)}@example.com`; // Set your own email;
+const PASSWORD = 'superCroc2019';
+
+const session = new Httpx({ baseURL: 'https://test-api.k6.io' });
+
+// Register a new user and retrieve authentication token for subsequent API requests
+export function setup() {
+ let authToken = null;
+
+ describe(`setup - create a test user ${USERNAME}`, () => {
+ const resp = session.post(`/user/register/`, {
+ first_name: 'Crocodile',
+ last_name: 'Owner',
+ username: USERNAME,
+ password: PASSWORD,
+ });
+
+ expect(resp.status, 'User create status').to.equal(201);
+ expect(resp, 'User create valid json response').to.have.validJsonBody();
+ });
+
+ describe(`setup - Authenticate the new user ${USERNAME}`, () => {
+ const resp = session.post(`/auth/token/login/`, {
+ username: USERNAME,
+ password: PASSWORD,
+ });
+
+ expect(resp.status, 'Authenticate status').to.equal(200);
+ expect(resp, 'Authenticate valid json response').to.have.validJsonBody();
+ authToken = resp.json('access');
+ expect(authToken, 'Authentication token').to.be.a('string');
+ });
+
+ return authToken;
+}
+
+export default function (authToken) {
+ // set the authorization header on the session for the subsequent requests
+ session.addHeader('Authorization', `Bearer ${authToken}`);
+
+ describe('01. Create a new crocodile', (t) => {
+ const payload = {
+ name: `Croc name ${randomString(10)}`,
+ sex: randomItem(['M', 'F']),
+ date_of_birth: '2023-05-11',
+ };
+
+ session.addTag('name', 'Create');
+ const resp = session.post(`/my/crocodiles/`, payload);
+
+ expect(resp.status, 'Croc creation status').to.equal(201);
+ expect(resp, 'Croc creation valid json response').to.have.validJsonBody();
+
+ session.newCrocId = resp.json('id');
+ });
+
+ describe('02. Fetch private crocs', (t) => {
+ session.clearTag('name');
+ const resp = session.get('/my/crocodiles/');
+
+ expect(resp.status, 'Fetch croc status').to.equal(200);
+ expect(resp, 'Fetch croc valid json response').to.have.validJsonBody();
+ expect(resp.json().length, 'Number of crocs').to.be.above(0);
+ });
+
+ describe('03. Update the croc', (t) => {
+ const payload = {
+ name: `New croc name ${randomString(10)}`,
+ };
+
+ const resp = session.patch(`/my/crocodiles/${session.newCrocId}/`, payload);
+
+ expect(resp.status, 'Croc patch status').to.equal(200);
+ expect(resp, 'Fetch croc valid json response').to.have.validJsonBody();
+ expect(resp.json('name'), 'Croc name').to.equal(payload.name);
+
+ // read "croc" again to verify the update worked
+ const resp1 = session.get(`/my/crocodiles/${session.newCrocId}/`);
+
+ expect(resp1.status, 'Croc fetch status').to.equal(200);
+ expect(resp1, 'Fetch croc valid json response').to.have.validJsonBody();
+ expect(resp1.json('name'), 'Croc name').to.equal(payload.name);
+ });
+
+ describe('04. Delete the croc', (t) => {
+ const resp = session.delete(`/my/crocodiles/${session.newCrocId}/`);
+
+ expect(resp.status, 'Croc delete status').to.equal(204);
+ });
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/bundling-and-transpilation.md b/docs/sources/v0.50.x/examples/bundling-and-transpilation.md
new file mode 100644
index 000000000..dac710512
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/bundling-and-transpilation.md
@@ -0,0 +1,10 @@
+---
+title: 'Bundling and transpilation'
+redirect: 'https://github.com/k6io/k6-es6/'
+description: |
+ Reference project demonstrating how to use webpack and babel to bundle
+ node modules or transpile code to ES5.1+ for usage in k6 tests.
+weight: 18
+---
+
+# Bundling and transpilation
diff --git a/docs/sources/v0.50.x/examples/cookies-example.md b/docs/sources/v0.50.x/examples/cookies-example.md
new file mode 100644
index 000000000..81d65d9c1
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/cookies-example.md
@@ -0,0 +1,198 @@
+---
+title: 'Cookies Example'
+description: 'Scripting examples on how you can interact with cookies during your load test if required.'
+weight: 08
+---
+
+# Cookies Example
+
+Scripting examples on how you can interact with cookies during your load test if required.
+
+## Cookies
+
+As HTTP is a stateless protocol, cookies are used by server-side applications to persist data
+on client machines. This is used more or less everywhere on the web, commonly for user session
+tracking purposes. In k6 cookies are managed automatically by default, however, there are use
+cases where access to read and manipulate cookies are required.
+
+## From the response headers
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check, group } from 'k6';
+
+export default function () {
+ // Since this request redirects the `res.cookies` property won't contain the cookies
+ const res = http.get('https://httpbin.test.k6.io/cookies/set?name1=value1&name2=value2');
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ });
+
+ // Make sure cookies have been added to VU cookie jar
+ const vuJar = http.cookieJar();
+ const cookiesForURL = vuJar.cookiesForURL(res.url);
+ check(null, {
+ "vu jar has cookie 'name1'": () => cookiesForURL.name1.length > 0,
+ "vu jar has cookie 'name2'": () => cookiesForURL.name2.length > 0,
+ });
+}
+```
+
+{{< /code >}}
+
+## Log all the cookies in a response
+
+> ### ⚠️ Note that this only works when using k6 locally
+>
+> The `console.log()` family of APIs are currently only usable when running k6 locally.
+> When running k6 tests with LoadImpact Cloud Execution the logs will be discarded.
+
+{{< code >}}
+
+```javascript
+// Example showing two methods how to log all cookies (with attributes) from a HTTP response.
+
+import http from 'k6/http';
+
+function logCookie(cookie) {
+ // Here we log the name and value of the cookie along with additional attributes.
+ // For full list of attributes see: https:///grafana.com/docs/k6/using-k6/cookies#properties-of-a-response-cookie-object
+ console.log(
+ `${cookie.name}: ${cookie.value}\n\tdomain: ${cookie.domain}\n\tpath: ${cookie.path}\n\texpires: ${cookie.expires}\n\thttpOnly: ${cookie.http_only}`
+ );
+}
+
+export default function () {
+ const res = http.get('https://www.google.com/');
+
+ // Method 1: Use for-loop and check for non-inherited properties
+ for (const name in res.cookies) {
+ if (res.cookies.hasOwnProperty(name) !== undefined) {
+ logCookie(res.cookies[name][0]);
+ }
+ }
+
+ // Method 2: Use ES6 Map to loop over Object entries
+ new Map(Object.entries(res.cookies)).forEach((v, k) => logCookie(v[0]));
+}
+```
+
+{{< /code >}}
+
+## Setting a cookie in the VU cookie jar
+
+To set a cookie that should be sent with every request matching a particular domain, path, etc.
+you'd do something like this:
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+export default function () {
+ // Get VU cookie jar and add a cookie to it providing the parameters
+ // that a request must match (domain, path, HTTPS or not etc.)
+ // to have the cookie attached to it when sent to the server.
+ const jar = http.cookieJar();
+ jar.set('https://httpbin.test.k6.io/cookies', 'my_cookie', 'hello world', {
+ domain: 'httpbin.test.k6.io',
+ path: '/cookies',
+ secure: true,
+ max_age: 600,
+ });
+
+ // As the following request is matching the above cookie in terms of domain,
+ // path, HTTPS (secure) and will happen within the specified "age" limit, the
+ // cookie will be attached to this request.
+ const res = http.get('https://httpbin.test.k6.io/cookies');
+ check(res, {
+ 'has status 200': (r) => r.status === 200,
+ "has cookie 'my_cookie'": (r) => r.json().cookies.my_cookie !== null,
+ 'cookie has correct value': (r) => r.json().cookies.my_cookie == 'hello world',
+ });
+}
+```
+
+{{< /code >}}
+
+## Delete a cookie in the VU cookie jar
+
+To delete a cookie in the jar for a specific URL and name, use the `delete` method.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+export default function () {
+ const jar = http.cookieJar();
+ jar.set('https://httpbin.test.k6.io/cookies', 'my_cookie_1', 'hello world_1');
+ jar.set('https://httpbin.test.k6.io/cookies', 'my_cookie_2', 'hello world_2');
+
+ const res1 = http.get('https://httpbin.test.k6.io/cookies');
+ check(res1, {
+ 'res1 has status 200': (r) => r.status === 200,
+ "res1 has cookie 'my_cookie_1'": (r) => r.json().cookies.my_cookie_1 !== null,
+ 'res1 cookie has correct value_1': (r) => r.json().cookies.my_cookie_1 == 'hello world_1',
+ "res1 has cookie 'my_cookie_2'": (r) => r.json().cookies.my_cookie_2 !== null,
+ 'res1 cookie has correct value_2': (r) => r.json().cookies.my_cookie_2 == 'hello world_2',
+ });
+
+ jar.delete('https://httpbin.test.k6.io/cookies', 'my_cookie_1');
+
+ const res2 = http.get('https://httpbin.test.k6.io/cookies');
+ check(res2, {
+ 'res2 has status 200': (r) => r.status === 200,
+ "res2 hasn't cookie 'my_cookie_1'": (r) => r.json().cookies.my_cookie_1 == null,
+ "res2 has cookie 'my_cookie_2'": (r) => r.json().cookies.my_cookie_2 !== null,
+ 'res2 cookie has correct value_2': (r) => r.json().cookies.my_cookie_2 == 'hello world_2',
+ });
+}
+```
+
+{{< /code >}}
+
+## Clear all cookies in the VU cookie jar
+
+To clear all cookies in the jar by specifying url, use the `clear` method.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+export default function () {
+ const jar = http.cookieJar();
+ jar.set('https://httpbin.test.k6.io/cookies', 'my_cookie', 'hello world');
+ const res1 = http.get('https://httpbin.test.k6.io/cookies');
+ check(res1, {
+ 'has status 200': (r) => r.status === 200,
+ "has cookie 'my_cookie'": (r) => r.json().cookies.my_cookie !== null,
+ 'cookie has correct value': (r) => r.json().cookies.my_cookie == 'hello world',
+ });
+
+ jar.clear('https://httpbin.test.k6.io/cookies');
+
+ const res2 = http.get('https://httpbin.test.k6.io/cookies');
+ check(res2, {
+ 'has status 200': (r) => r.status === 200,
+ "hasn't cookie 'my_cookie'": (r) => r.json().cookies.my_cookie == null,
+ });
+}
+```
+
+{{< /code >}}
+
+**Relevant k6 APIs**:
+
+- [http.cookieJar()](https://grafana.com/docs/k6//javascript-api/k6-http/cookiejar-method)
+- [http.CookieJar](https://grafana.com/docs/k6//javascript-api/k6-http/cookiejar)
+
+- [set(url, name, value, [options])](https://grafana.com/docs/k6//javascript-api/k6-http/cookiejar/cookiejar-set)
+- [delete(url, name)](https://grafana.com/docs/k6//javascript-api/k6-http/cookiejar/cookiejar-delete)
+- [clear(url)](https://grafana.com/docs/k6//javascript-api/k6-http/cookiejar/cookiejar-clear)
diff --git a/docs/sources/v0.50.x/examples/correlation-and-dynamic-data.md b/docs/sources/v0.50.x/examples/correlation-and-dynamic-data.md
new file mode 100644
index 000000000..d5cbe1dd9
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/correlation-and-dynamic-data.md
@@ -0,0 +1,149 @@
+---
+title: 'Correlation and Dynamic Data'
+slug: '/correlation-and-dynamic-data'
+description: |
+ Scripting examples on how to correlate dynamic data in your test script. Correlation is
+ often required when using the Chrome Extension or HAR converter to generate your test script.
+ This is due to the fact that those tools will capture session IDs, CSRF tokens, VIEWSTATE,
+ wpnonce, and other dynamic values from your specific session.
+weight: 04
+---
+
+# Correlation and Dynamic Data
+
+Scripting examples on how to correlate dynamic data in your test script. Correlation is often
+required when using the Chrome Extension or HAR converter to generate your test script. This
+is because those tools will capture session IDs, CSRF tokens, VIEWSTATE, wpnonce, and other
+dynamic values from your specific session. These tokens typically expire very quickly. This
+is one of the most common things that users will script for when testing user journeys across
+websites or web apps.
+
+### Correlation
+
+In a load testing scenario, correlation means extracting one or more values from the response
+of one request and then reusing them in subsequent requests. Often, this could be getting
+a token or some sort of ID necessary to fulfill a sequence of steps in a user journey.
+
+A [recording](https://grafana.com/docs/k6//using-k6/test-authoring/create-tests-from-recordings) will capture session data such as CSRF tokens,
+VIEWSTATES, nonce, etc. This type of data is unlikely to be valid when
+you run your test, meaning you'll need to handle the extraction of this data from the HTML/form
+to include it in subsequent requests. This issue is fairly common with any site that has forms
+and can be handled with a little bit of scripting.
+
+### Extracting values/tokens from a JSON response
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+export default function () {
+ // Make a request that returns some JSON data
+ const res = http.get('https://httpbin.test.k6.io/json');
+
+ // Extract data from that JSON data by first parsing it
+ // using a call to "json()" and then accessing properties by
+ // navigating the JSON data as a JS object with dot notation.
+ const slide1 = res.json().slideshow.slides[0];
+ check(slide1, {
+ 'slide 1 has correct title': (s) => s.title === 'Wake up to WonderWidgets!',
+ 'slide 1 has correct type': (s) => s.type === 'all',
+ });
+
+ // Now we could use the "slide1" variable in subsequent requests...
+}
+```
+
+{{< /code >}}
+
+**Relevant k6 APIs**:
+
+- [Response.json()](https://grafana.com/docs/k6//javascript-api/k6-http/response)
+
+- [JSON.parse()](https://developer.mozilla.org/en-US/Web/JavaScript/Reference/Global_Objects/JSON/parse)
+ (An alternative API that can be used for parsing JSON data)
+
+### Extracting values/tokens from form fields
+
+You can choose from two different approaches when deciding how to handle form submissions.
+Either you use the higher-level [Response.submitForm([params])](https://grafana.com/docs/k6//javascript-api/k6-http/response/response-submitform) API
+or you extract necessary hidden fields etc. and build a request yourself and then send it using the
+appropriate `http.*` family of APIs, like [http.post(url, [body], [params])](https://grafana.com/docs/k6//javascript-api/k6-http/post).
+
+#### Extracting .NET ViewStates, CSRF tokens and other hidden input fields
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { sleep } from 'k6';
+
+export default function () {
+ // Request the page containing a form and save the response. This gives you access
+ //to the response object, `res`.
+ const res = http.get('https://test.k6.io/my_messages.php', { responseType: 'text' });
+
+ // Query the HTML for an input field named "redir". We want the value or "redir"
+ const elem = res.html().find('input[name=redir]');
+
+ // Get the value of the attribute "value" and save it to a variable
+ const val = elem.attr('value');
+
+ // Now you can concatenate this extracted value in subsequent requests that require it.
+ // ...
+ // console.log() works when executing k6 scripts locally and is handy for debugging purposes
+ console.log('The value of the hidden field redir is: ' + val);
+
+ sleep(1);
+}
+```
+
+{{< /code >}}
+
+> ### ⚠️ Did you know?
+>
+> Take note if `discardResponseBodies` is set to true in the options
+> section of your script. If it is, you can either make it `false` or save the response per
+> request with `{"responseType": "text"}` as shown in the example.
+
+**Relevant k6 APIs**:
+
+- [Selection.find(selector)](https://grafana.com/docs/k6//javascript-api/k6-html/selection/selection-find) (the [jQuery Selector API](http://api.jquery.com/category/selectors/)
+ docs are also a good resource on what possible selector queries can be made)
+- [Selection.attr(name)](https://grafana.com/docs/k6//javascript-api/k6-html/selection/selection-attr)
+
+### Generic extraction of values/tokens
+
+Sometimes, responses may be neither JSON nor HTML, in which case the above extraction methods would not apply. In these situations, you would likely want to operate directly on the `Response.body` string using a simple function capable of extracting a string at some known location. This is typically achieved by looking for the string "boundaries" immediately before (left) and after (right) the value needing extraction.
+
+The [jslib utils](https://grafana.com/docs/k6//javascript-api/jslib/utils) library contains an example of this kind of function,[findBetween](https://grafana.com/docs/k6//javascript-api/jslib/utils/findbetween). The function uses the JavaScript built-in [String.indexOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf) and therefore doesn't depend on potentially expensive regular-expression operations.
+
+#### Extracting a value/token using findBetween
+
+{{< code >}}
+
+```javascript
+import { findBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
+import { check } from 'k6';
+import http from 'k6/http';
+
+export default function () {
+ // This request returns XML:
+ const res = http.get('https://httpbin.test.k6.io/xml');
+
+ // Use findBetween to extract the first title encountered:
+ const title = findBetween(res.body, '', '');
+
+ check(title, {
+ 'title is correct': (t) => t === 'Wake up to WonderWidgets!',
+ });
+}
+```
+
+{{< /code >}}
+
+**Relevant k6 APIs**:
+
+- [Response.body](https://grafana.com/docs/k6//javascript-api/k6-http/response)
+- [findBetween(content, left, right)](https://grafana.com/docs/k6//javascript-api/jslib/utils/findbetween)
diff --git a/docs/sources/v0.50.x/examples/crawl-webpage.md b/docs/sources/v0.50.x/examples/crawl-webpage.md
new file mode 100644
index 000000000..e1ac98849
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/crawl-webpage.md
@@ -0,0 +1,9 @@
+---
+title: 'Crawl a web page'
+redirect: 'https://stackoverflow.com/questions/60927653/downloading-whole-websites-with-k6/'
+description: |
+ Stack overflow answer demonstrating how to crawl a web page
+weight: 17
+---
+
+# Crawl a web page
diff --git a/docs/sources/v0.50.x/examples/data-generation.md b/docs/sources/v0.50.x/examples/data-generation.md
new file mode 100644
index 000000000..411f6c140
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/data-generation.md
@@ -0,0 +1,9 @@
+---
+title: 'Generating realistic data'
+redirect: 'https://github.com/k6io/example-data-generation/'
+description: |
+ Reference project demonstrating how to generate data with realistic traits at runtime using faker.js
+weight: 16
+---
+
+# Generating realistic data
diff --git a/docs/sources/v0.50.x/examples/data-parameterization.md b/docs/sources/v0.50.x/examples/data-parameterization.md
new file mode 100644
index 000000000..f084b2ee6
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/data-parameterization.md
@@ -0,0 +1,256 @@
+---
+title: 'Data Parameterization'
+description: |
+ Scripting examples on how to parameterize data in a test script. Parameterization is typically
+ necessary when Virtual Users(VUs) will make a POST, PUT, or PATCH request in a test.
+
+ Parameterization helps to prevent server-side caching from impacting your load test.
+ This will, in turn, make your test more realistic.
+weight: 05
+---
+
+# Data Parameterization
+
+_Data parameterization_ is the process of turning test values into reusable parameters, for example, through variables and shared arrays.
+
+This page gives some examples of how to parameterize data in a test script.
+Parameterization is typically necessary when Virtual Users (VUs) will make a POST, PUT, or PATCH request in a test.
+You can also use parameterization when you need to add test data from a separate file.
+
+Parameterization helps to prevent server-side caching from impacting your load test.
+This will, in turn, make your test more realistic.
+
+## Performance implications of `SharedArray`
+
+Each VU in k6 is a separate JS VM. To prevent multiple copies of the whole data file,
+[SharedArray](https://grafana.com/docs/k6//javascript-api/k6-data/sharedarray) was added. It does have some CPU overhead in accessing elements compared to a normal non shared
+array, but the difference is negligible compared to the time it takes to make requests. This becomes
+even less of an issue compared to not using it with large files, as k6 would otherwise use too much memory to run, which might lead to your script not being able to run at all or aborting in the middle if the system resources are exhausted.
+
+For example, the Cloud service allocates 8GB of memory for every 300 VUs. So if your files are large
+enough and you are not using [SharedArray](https://grafana.com/docs/k6//javascript-api/k6-data/sharedarray), that might mean that your script will run out of memory at
+some point. Additionally even if there is enough memory, k6 has a garbage collector (as it's written
+in golang) and it will walk through all accessible objects (including JS ones) and figure out which
+need to be garbage collected. For big JS arrays copied hundreds of times this adds quite a lot of
+additional work.
+
+A note on performance characteristics of `SharedArray` can be found within its [API documentation](https://grafana.com/docs/k6//javascript-api/k6-data/sharedarray#performance-characteristics).
+
+## From a JSON file
+
+{{< code >}}
+
+```json
+{
+ "users": [
+ { "username": "test", "password": "qwerty" },
+ { "username": "test", "password": "qwerty" }
+ ]
+}
+```
+
+{{< /code >}}
+
+{{< code >}}
+
+```javascript
+import { SharedArray } from 'k6/data';
+// not using SharedArray here will mean that the code in the function call (that is what loads and
+// parses the json) will be executed per each VU which also means that there will be a complete copy
+// per each VU
+const data = new SharedArray('some data name', function () {
+ return JSON.parse(open('./data.json')).users;
+});
+
+export default function () {
+ const user = data[0];
+ console.log(data[0].username);
+}
+```
+
+{{< /code >}}
+
+## From a CSV file
+
+k6 doesn't parse CSV files natively, but you can use an external library, [Papa Parse](https://www.papaparse.com/).
+
+You can download the library and import it locally like this:
+
+{{< code >}}
+
+```javascript
+import papaparse from './papaparse.js';
+import { SharedArray } from 'k6/data';
+// not using SharedArray here will mean that the code in the function call (that is what loads and
+// parses the csv) will be executed per each VU which also means that there will be a complete copy
+// per each VU
+const csvData = new SharedArray('another data name', function () {
+ // Load CSV file and parse it using Papa Parse
+ return papaparse.parse(open('./data.csv'), { header: true }).data;
+});
+
+export default function () {
+ // ...
+}
+```
+
+{{< /code >}}
+
+Or you can grab it directly from [jslib.k6.io](https://jslib.k6.io/) like this.
+
+{{< code >}}
+
+```javascript
+import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
+import { SharedArray } from 'k6/data';
+
+// not using SharedArray here will mean that the code in the function call (that is what loads and
+// parses the csv) will be executed per each VU which also means that there will be a complete copy
+// per each VU
+const csvData = new SharedArray('another data name', function () {
+ // Load CSV file and parse it using Papa Parse
+ return papaparse.parse(open('./data.csv'), { header: true }).data;
+});
+
+export default function () {
+ // ...
+}
+```
+
+{{< /code >}}
+
+Here's an example using Papa Parse to parse a CSV file of username/password pairs and using that
+data to login to the test.k6.io test site:
+
+{{< code >}}
+
+```javascript
+/* Where contents of data.csv is:
+username,password
+admin,123
+test_user,1234
+*/
+import http from 'k6/http';
+import { check, sleep } from 'k6';
+import { SharedArray } from 'k6/data';
+import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
+
+// not using SharedArray here will mean that the code in the function call (that is what loads and
+// parses the csv) will be executed per each VU which also means that there will be a complete copy
+// per each VU
+const csvData = new SharedArray('another data name', function () {
+ // Load CSV file and parse it using Papa Parse
+ return papaparse.parse(open('./data.csv'), { header: true }).data;
+});
+
+export default function () {
+ // Now you can use the CSV data in your test logic below.
+ // Below are some examples of how you can access the CSV data.
+
+ // Loop through all username/password pairs
+ for (const userPwdPair of csvData) {
+ console.log(JSON.stringify(userPwdPair));
+ }
+
+ // Pick a random username/password pair
+ const randomUser = csvData[Math.floor(Math.random() * csvData.length)];
+ console.log('Random user: ', JSON.stringify(randomUser));
+
+ const params = {
+ login: randomUser.username,
+ password: randomUser.password,
+ };
+ console.log('Random user: ', JSON.stringify(params));
+
+ const res = http.post('https://test.k6.io/login.php', params);
+ check(res, {
+ 'login succeeded': (r) => r.status === 200 && r.body.indexOf('successfully authorized') !== -1,
+ });
+
+ sleep(1);
+}
+```
+
+{{< /code >}}
+
+## Retrieving unique data
+
+It is often a requirement not to use the same data more than once in a test. With the help of [k6/execution](https://grafana.com/docs/k6//javascript-api/k6-execution), which includes a property `scenario.iterationInTest`, you can retrieve unique rows from your data set.
+
+> ### ⚠️ Multiple scenarios
+>
+> `scenario.iterationInTest` property is unique **per scenario**, not the overall test.
+> That means if you have multiple scenarios in your test you might need to split your data per scenario.
+
+{{< code >}}
+
+```javascript
+import { SharedArray } from 'k6/data';
+import { scenario } from 'k6/execution';
+
+const data = new SharedArray('users', function () {
+ return JSON.parse(open('./data.json')).users;
+});
+
+export const options = {
+ scenarios: {
+ 'use-all-the-data': {
+ executor: 'shared-iterations',
+ vus: 10,
+ iterations: data.length,
+ maxDuration: '1h',
+ },
+ },
+};
+
+export default function () {
+ // this is unique even in the cloud
+ const user = data[scenario.iterationInTest];
+ console.log(`user: ${JSON.stringify(user)}`);
+}
+```
+
+{{< /code >}}
+
+Alternatively, if your use case requires using a unique data set per VU, you could leverage a property called `vu.idInTest`.
+
+In the following example we're going to be using `per-vu-iterations` executor to ensure that every VU completes
+a fixed amount of iterations.
+
+{{< code >}}
+
+```javascript
+import { sleep } from 'k6';
+import { SharedArray } from 'k6/data';
+import { vu } from 'k6/execution';
+
+const users = new SharedArray('users', function () {
+ return JSON.parse(open('./data.json')).users;
+});
+
+export const options = {
+ scenarios: {
+ login: {
+ executor: 'per-vu-iterations',
+ vus: users.length,
+ iterations: 20,
+ maxDuration: '1h30m',
+ },
+ },
+};
+
+export default function () {
+ // VU identifiers are one-based and arrays are zero-based, thus we need - 1
+ console.log(`Users name: ${users[vu.idInTest - 1].username}`);
+ sleep(1);
+}
+```
+
+{{< /code >}}
+
+## Generating data using faker.js
+
+The following articles show how to use faker.js in k6 to generate realistic data during the test execution:
+
+- [Performance Testing with Generated Data using k6 and Faker](https://dev.to/k6/performance-testing-with-generated-data-using-k6-and-faker-2e)
+- [Load Testing Made Easy with K6: Using Faker Library and CSV Files](https://farhan-labib.medium.com/load-testing-made-easy-with-k6-using-faker-library-and-csv-files-c997d48fb6e2)
diff --git a/docs/sources/v0.50.x/examples/data-uploads.md b/docs/sources/v0.50.x/examples/data-uploads.md
new file mode 100644
index 000000000..211cb8cc2
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/data-uploads.md
@@ -0,0 +1,150 @@
+---
+title: 'Data Uploads'
+description: 'Scripting examples on how to execute a load test that will upload a file to the System Under Test (SUT).'
+weight: 09
+---
+
+# Data Uploads
+
+Example to execute a load test that will upload a file to the System Under Test (SUT).
+
+## The open() function
+
+Using the built-in function [`open()`](https://grafana.com/docs/k6//javascript-api/init-context/open),
+we can read the contents of a file given a filename or URL.
+
+Below is a simple example showing how to load the contents of a local file `data.json`.
+
+{{< code >}}
+
+```json
+{
+ "my_key": "has a value"
+}
+```
+
+{{< /code >}}
+
+{{< code >}}
+
+```javascript
+const data = JSON.parse(open('./data.json'));
+
+export default function () {
+ console.log(data.my_key);
+}
+```
+
+{{< /code >}}
+
+If you want to open a binary file you need to pass in `"b"` as the second argument.
+
+{{< code >}}
+
+```javascript
+const binFile = open('./image.png', 'b');
+
+export default function () {
+ //...
+}
+```
+
+{{< /code >}}
+
+## Multipart request (uploading a file)
+
+Now that you know how to load a local file, let's look at a script that creates a POST request
+to upload this data to an API endpoint along with a regular text field (`field` in the example
+below):
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { sleep } from 'k6';
+
+const binFile = open('/path/to/file.bin', 'b');
+
+export default function () {
+ const data = {
+ field: 'this is a standard form field',
+ file: http.file(binFile, 'test.bin'),
+ };
+
+ const res = http.post('https://example.com/upload', data);
+ sleep(3);
+}
+```
+
+{{< /code >}}
+
+In the example above we use the [http.file()](https://grafana.com/docs/k6//javascript-api/k6-http/file)
+API to wrap the file contents in a [FileData](https://grafana.com/docs/k6//javascript-api/k6-http/filedata) object.
+When passing a JS object as the body parameter to [http.post()](https://grafana.com/docs/k6//javascript-api/k6-http/post),
+or any of the other HTTP request functions, where one of the property values is a
+[FileData](https://grafana.com/docs/k6//javascript-api/k6-http/filedata) a multipart request will be constructed
+and sent.
+
+### Relevant k6 APIs
+
+- [open(filePath, [mode])](https://grafana.com/docs/k6//javascript-api/init-context/open)
+- [http.file(data, [filename], [contentType])](https://grafana.com/docs/k6//javascript-api/k6-http/file)
+
+## Advanced multipart request
+
+The previous multipart request example has some limitations:
+
+- It's not possible to assemble the parts in a specific order, because of the
+ unordered nature of JS objects when they're converted to Golang maps, which k6 uses internally.
+ Uploading files in a specific order is a requirement for some APIs (e.g. AWS S3).
+- It's not possible to upload multiple files as part of the same form field, because
+ JS object keys must be unique.
+
+To address this we suggest using the [`FormData` polyfill for k6](https://jslib.k6.io/formdata/0.0.2/index.js).
+
+Here's an example of uploading several binary files and a text file using the polyfill:
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+import { FormData } from 'https://jslib.k6.io/formdata/0.0.2/index.js';
+
+const img1 = open('/path/to/image1.png', 'b');
+const img2 = open('/path/to/image2.jpg', 'b');
+const txt = open('/path/to/text.txt');
+
+export default function () {
+ const fd = new FormData();
+ fd.append('someTextField', 'someValue');
+ fd.append('aBinaryFile', {
+ data: new Uint8Array(img1).buffer,
+ filename: 'logo.png',
+ content_type: 'image/png',
+ });
+ fd.append('anotherTextField', 'anotherValue');
+ fd.append('images', http.file(img1, 'image1.png', 'image/png'));
+ fd.append('images', http.file(img2, 'image2.jpg', 'image/jpeg'));
+ fd.append('text', http.file(txt, 'text.txt', 'text/plain'));
+
+ const res = http.post('https://httpbin.test.k6.io/post', fd.body(), {
+ headers: { 'Content-Type': 'multipart/form-data; boundary=' + fd.boundary },
+ });
+ check(res, {
+ 'is status 200': (r) => r.status === 200,
+ });
+}
+```
+
+{{< /code >}}
+
+Note that:
+
+- Both binary files will be uploaded under the `images` form field, and the text file
+ will appear last in the request under the `text` form field.
+- It's required to specify the multipart boundary in the `Content-Type` header,
+ so you must assemble the header manually as shown.
+- Blob is not supported or implemented. For the same functionality, use
+ a simple object with the fields `data`, `content_type` (defaulting to "application/octet-stream") and optionally
+ `filename` as shown for `aBinaryFile` above.
diff --git a/docs/sources/v0.50.x/examples/distribute-workloads.md b/docs/sources/v0.50.x/examples/distribute-workloads.md
new file mode 100644
index 000000000..173a9a64e
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/distribute-workloads.md
@@ -0,0 +1,160 @@
+---
+title: Distribute workloads across VUs
+description: How to configure different amounts of traffic for different VU behaviors
+slug: /distribute-workloads
+weight: 24
+---
+
+# Distribute workloads across VUs
+
+k6 can schedule different load patterns for different VU functions.
+A test with multiple workloads might better simulate traffic in the real world, where user behavior is rarely uniform.
+For example, most traffic to an e-commerce site might come from users who only search for items and read reviews. A small percentage of users might actively shop, performing actions that involve writes to the database and calls to different APIs.
+
+The following sections provide examples of how to structure k6 scripts to split logic across VUs.
+To inspect the results for a certain behavior, you can [create a custom metric](https://grafana.com/docs/k6//using-k6/metrics/create-custom-metrics) or use [Tags](https://grafana.com/docs/k6//using-k6/tags-and-groups) to filter by scenario, code block, or individual request.
+
+{{% admonition type="note" %}}
+
+These techniques can create very complex configurations.
+However, more complexity creates more ambiguity in result interpretation
+
+{{% /admonition %}}
+
+## Split logic across scenarios
+
+{{% admonition type="note" %}}
+
+In this context, _workload_ refers to the traffic pattern simulated by a scenario.
+
+{{% /admonition %}}
+
+One way to distribute traffic is to use scenarios to schedule different workloads for different functions.
+
+1. Define multiple scenarios in your [options](https://grafana.com/docs/k6//using-k6/k6-options).
+1. Use the scenario `exec` property to execute different VU functions with a workload.
+
+For example, imagine a social media site that typically receives 100 concurrent users.
+Of those, 80 might visit their contacts page, and 20 might view the news.
+To configure such a distribution, make two scenarios with different throughput or VUs:
+
+```javascript
+import http from 'k6/http';
+
+export const options = {
+ //scenario to view contacts
+ scenarios: {
+ contacts: {
+ executor: 'shared-iterations',
+ exec: 'contacts',
+ vus: 80,
+ iterations: 100,
+ },
+ //scenario to view news
+ news: {
+ executor: 'shared-iterations',
+ exec: 'news',
+ vus: 20,
+ iterations: 100,
+ },
+ },
+};
+
+//use the exec property to run different scenarios for different functions
+
+export function contacts() {
+ http.get('https://test.k6.io/contacts.php');
+}
+
+export function news() {
+ http.get('https://test.k6.io/news.php');
+}
+```
+
+To view granular results for a specific scenario, you can filter by the built-in scenario [tag](https://grafana.com/docs/k6//using-k6/tags-and-groups).
+
+## Distribute logic by VU ID
+
+In some cases, writing a scenario for each behavior might be inconvenient or impractical.
+As an alternative, you can distribute logic across a range of VUs with the [execution context variables](https://grafana.com/docs/k6//using-k6/execution-context-variables) from the [`k6/execution`](https://grafana.com/docs/k6//javascript-api/k6-execution) module.
+With the `exec` object, you can scope logic to a specific instance, scenario, or across all VUs.
+
+For example, this statement assigns behavior to the first 25 VUs in a test.
+
+```bash
+if (exec.vu.idInTest <= 25) {
+ //do something;
+ }
+```
+
+For more flexibility, you can use modulo expressions to distribute VUs according to percentages.
+For example, the following script distributes logic according to different user profiles:
+
+- 40 percent of users check the news.
+- 60 percent play a coinflip game.
+ - Half bet `heads`, and half bet `tails`.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import exec from 'k6/execution';
+
+export const options = {
+ scenarios: {
+ quickRamp: {
+ executor: 'ramping-arrival-rate',
+ startRate: 0,
+ timeUnit: '1s',
+ preAllocatedVUs: 100,
+ stages: [
+ { target: 10, duration: '10s' },
+ { target: 10, duration: '15s' },
+ { target: 0, duration: '5s' },
+ ],
+ },
+ },
+};
+
+export default function () {
+ if (exec.vu.idInTest % 10 < 4) {
+ // 0-3 range, read the news
+ http.get('http://test.k6.io/news');
+ } else if (exec.vu.idInTest % 10 < 7) {
+ // 4-6 range, bet heads
+ http.get('http://test.k6.io/flip_coin.php?bet=heads');
+ } else {
+ // 7-9 range, bet tails
+ http.get('http://test.k6.io/flip_coin.php?bet=tails');
+ }
+}
+```
+
+To view results for a specific request or group, you can define [tags](https://grafana.com/docs/k6//using-k6/tags-and-groups).
+
+{{< /code >}}
+
+## Randomize behavior
+
+To add a degree of random behavior, consider one of the randomizing functions from the [k6 utils](https://grafana.com/docs/k6//javascript-api/jslib/utils).
+
+For example, this script randomly assigns one behavior to happen one-third of the time, and another to happen all other times.
+
+{{< code >}}
+
+```javascript
+import { sleep } from 'k6';
+import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
+
+export default function () {
+ if (randomIntBetween(1, 3) % 3 > 1) {
+ console.log('1 in 3 times');
+ } else {
+ console.log('2 in 3 times');
+ }
+}
+```
+
+{{< /code >}}
+
+For a more sophisticated example of randomizing, read this [forum post](https://community.grafana.com/t/how-to-distribute-vus-across-different-scenarios-with-k6/97698/17).
diff --git a/docs/sources/v0.50.x/examples/error-handler.md b/docs/sources/v0.50.x/examples/error-handler.md
new file mode 100644
index 000000000..9b1a6cfec
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/error-handler.md
@@ -0,0 +1,74 @@
+---
+title: 'Error handler'
+description: Using a custom error handler to store errors in custom metrics or logs.
+weight: 25
+---
+
+# Error handler
+
+When encountering errors from an application's backend, gathering additional error information, such as error messages, tracing data, or request and response bodies, is often necessary. The initial suggestion is to leverage your observability solution to find these errors. However, capturing error details directly in k6 can also be useful for troubleshooting.
+
+In k6, there are two common approaches to store additional error information:
+
+- Console logs and output [k6 logs to Loki](https://k6.io/blog/using-loki-to-store-and-query-k6-logs/), a file, or use [Grafana Cloud k6](https://grafana.com/docs/grafana-cloud/k6/analyze-results/inspect-test-results/inspect-logs/).
+- Using [Tags](https://grafana.com/docs/k6//using-k6/tags-and-groups/) in a [custom counter metric](https://grafana.com/docs/k6//using-k6/metrics/create-custom-metrics) to track error data.
+
+When deciding what error data to store, consider whether it's a general or specific error, and be aware that high-load tests may generate a substantial volume of error data.
+
+Below is an example using an `ErrorHandler` class to log error information. It accepts a callback that instructs how to log errors. The example provides instructions for both previously mentioned options: console logs and custom metrics.
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+// General error handler to log error details.
+class ErrorHandler {
+ // Instruct the error handler how to log errors
+ constructor(logErrorDetails) {
+ this.logErrorDetails = logErrorDetails;
+ }
+
+ // Logs response error details if isError is true.
+ logError(isError, res, tags = {}) {
+ if (!isError) return;
+
+ // the Traceparent header is a W3C Trace Context
+ const traceparentHeader = res.request.headers['Traceparent'];
+
+ // Add any other useful information
+ const errorData = Object.assign(
+ {
+ url: res.url,
+ status: res.status,
+ error_code: res.error_code,
+ traceparent: traceparentHeader && traceparentHeader.toString(),
+ },
+ tags
+ );
+ this.logErrorDetails(errorData);
+ }
+}
+
+// Set up the error handler to log errors to the console
+const errorHandler = new ErrorHandler((error) => {
+ console.error(error);
+});
+
+/* Alternatively, you may log errors using a custom metric or any other option.
+const errors = new CounterMetric('errors');
+const errorHandler = new ErrorHandler((error) => {errors.add(1, error);});
+*/
+
+// Basic example to demonstrate the error handler
+export default function () {
+ let res, checkStatus;
+
+ res = http.get('https://httpbin.org/status/200');
+ checkStatus = check(res, { 'status is 200': (res) => res.status === 200 });
+ errorHandler.logError(!checkStatus, res);
+
+ res = http.get('https://httpbin.org/status/300');
+ checkStatus = check(res, { 'status is 200': (res) => res.status === 200 });
+ errorHandler.logError(!checkStatus, res);
+}
+```
diff --git a/docs/sources/v0.50.x/examples/functional-testing.md b/docs/sources/v0.50.x/examples/functional-testing.md
new file mode 100644
index 000000000..eb661827c
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/functional-testing.md
@@ -0,0 +1,1650 @@
+---
+title: 'Functional testing'
+description: |
+ Use Chaijs library for functional and integration testing.
+weight: 19
+---
+
+# Functional testing
+
+### Most basic integration test
+
+{{< code >}}
+
+
+
+```javascript
+import http from 'k6/http';
+import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js';
+
+export const options = {
+ thresholds: {
+ checks: ['rate == 1.00'],
+ },
+};
+
+export default function () {
+ describe('Hello world!', () => {
+ const response = http.get('https://test-api.k6.io/public/crocodiles/');
+
+ expect(response.status, 'response status').to.equal(200);
+ expect(response).to.have.validJsonBody();
+ expect(response.json(), 'croc list').to.be.an('array');
+ });
+}
+```
+
+{{< /code >}}
+
+### Sample integration test
+
+This test goes through several steps. It creates a new user account, authenticates, and interacts with protected resources.
+
+{{< code >}}
+
+
+
+```javascript
+import chai, { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js';
+import { Httpx, Get } from 'https://jslib.k6.io/httpx/0.0.6/index.js';
+import { randomString } from 'https://jslib.k6.io/k6-utils/1.0.0/index.js';
+
+chai.config.logFailures = true;
+
+export let options = {
+ thresholds: {
+ // fail the test if any checks fail or any requests fail
+ checks: ['rate == 1.00'],
+ http_req_failed: ['rate == 0.00'],
+ },
+ vus: 1,
+ iterations: 1,
+};
+
+let session = new Httpx({ baseURL: 'https://test-api.k6.io' });
+
+function retrieveIndividualCrocodilesInABatch() {
+ describe('[Crocs service] Fetch public crocs one by one', () => {
+ let responses = session.batch([
+ new Get('/public/crocodiles/1/'),
+ new Get('/public/crocodiles/2/'),
+ new Get('/public/crocodiles/3/'),
+ ]);
+
+ expect(responses, 'responses').to.be.an('array');
+
+ responses.forEach((response) => {
+ expect(response.status, 'response status').to.equal(200);
+ expect(response).to.have.validJsonBody();
+ expect(response.json(), 'crocodile').to.be.an('object');
+ expect(response.json(), 'crocodile').to.include.keys('age', 'name', 'id', 'sex');
+ expect(response.json(), 'crocodile').to.not.have.a.property('outfit');
+ });
+ });
+}
+
+function retrieveAllPublicCrocodiles() {
+ describe('[Crocs service] Fetch a list of crocs', () => {
+ let response = session.get('/public/crocodiles');
+
+ expect(response.status, 'response status').to.equal(200);
+ expect(response).to.have.validJsonBody();
+ expect(response.json(), 'croc list').to.be.an('array').lengthOf.above(5);
+ });
+}
+
+function validateAuthService() {
+ const USERNAME = `${randomString(10)}@example.com`;
+ const PASSWORD = 'superCroc2021';
+
+ describe('[Registration service] user registration', () => {
+ let sampleUser = {
+ username: USERNAME,
+ password: PASSWORD,
+ email: USERNAME,
+ first_name: 'John',
+ last_name: 'Smith',
+ };
+
+ let response = session.post(`/user/register/`, sampleUser);
+
+ expect(response.status, 'registration status').to.equal(201);
+ expect(response).to.have.validJsonBody();
+ });
+
+ describe('[Auth service] user authentication', () => {
+ let authData = {
+ username: USERNAME,
+ password: PASSWORD,
+ };
+
+ let resp = session.post(`/auth/token/login/`, authData);
+
+ expect(resp.status, 'Auth status').to.be.within(200, 204);
+ expect(resp).to.have.validJsonBody();
+ expect(resp.json()).to.have.a.property('access');
+ expect(resp.json('access'), 'auth token').to.be.a('string');
+
+ let authToken = resp.json('access');
+ // set the authorization header on the session for the subsequent requests.
+ session.addHeader('Authorization', `Bearer ${authToken}`);
+ });
+}
+
+function validateCrocodileCreation() {
+ // authentication happened before this call.
+
+ describe('[Croc service] Create a new crocodile', () => {
+ let payload = {
+ name: `Croc Name`,
+ sex: 'M',
+ date_of_birth: '2019-01-01',
+ };
+
+ let resp = session.post(`/my/crocodiles/`, payload);
+
+ expect(resp.status, 'Croc creation status').to.equal(201);
+ expect(resp).to.have.validJsonBody();
+
+ session.newCrocId = resp.json('id'); // caching croc ID for the future.
+ });
+
+ describe('[Croc service] Fetch private crocs', () => {
+ let response = session.get('/my/crocodiles/');
+
+ expect(response.status, 'response status').to.equal(200);
+ expect(response, 'private crocs').to.have.validJsonBody();
+ expect(response.json(), 'private crocs').to.not.be.empty;
+ });
+}
+
+export default function testSuite() {
+ retrieveIndividualCrocodilesInABatch();
+ retrieveAllPublicCrocodiles();
+ validateAuthService();
+ validateCrocodileCreation();
+}
+```
+
+{{< /code >}}
+
+### Full example showcasing all functionality
+
+Here's an auto-generated k6 test script showcasing all examples from the [Chaijs API documentation](https://www.chaijs.com/api/bdd/).
+
+{{< code >}}
+
+
+
+```javascript
+import { describe, expect, chai } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js';
+
+chai.config.aggregateChecks = false;
+chai.config.logFailures = true;
+
+export default function testSuite() {
+ describe('docs example 0', () => {
+ expect(function () {}).to.not.throw();
+ expect({ a: 1 }).to.not.have.property('b');
+ expect([1, 2]).to.be.an('array').that.does.not.include(3);
+ });
+
+ describe('docs example 1', () => {
+ expect(2).to.equal(2); // Recommended
+ expect(2).to.not.equal(1); // Not recommended
+ });
+
+ describe('docs example 2', () => {
+ // Target object deeply (but not strictly) equals `{a: 1}`
+ expect({ a: 1 }).to.deep.equal({ a: 1 });
+ expect({ a: 1 }).to.not.equal({ a: 1 });
+
+ // Target array deeply (but not strictly) includes `{a: 1}`
+ expect([{ a: 1 }]).to.deep.include({ a: 1 });
+ expect([{ a: 1 }]).to.not.include({ a: 1 });
+
+ // Target object deeply (but not strictly) includes `x: {a: 1}`
+ expect({ x: { a: 1 } }).to.deep.include({ x: { a: 1 } });
+ expect({ x: { a: 1 } }).to.not.include({ x: { a: 1 } });
+
+ // Target array deeply (but not strictly) has member `{a: 1}`
+ expect([{ a: 1 }]).to.have.deep.members([{ a: 1 }]);
+ expect([{ a: 1 }]).to.not.have.members([{ a: 1 }]);
+
+ // Target set deeply (but not strictly) has key `{a: 1}`
+ expect(new Set([{ a: 1 }])).to.have.deep.keys([{ a: 1 }]);
+ expect(new Set([{ a: 1 }])).to.not.have.keys([{ a: 1 }]);
+
+ // Target object deeply (but not strictly) has property `x: {a: 1}`
+ expect({ x: { a: 1 } }).to.have.deep.property('x', { a: 1 });
+ expect({ x: { a: 1 } }).to.not.have.property('x', { a: 1 });
+ });
+
+ describe('docs example 3', () => {
+ expect({ a: { b: ['x', 'y'] } }).to.have.nested.property('a.b[1]');
+ expect({ a: { b: ['x', 'y'] } }).to.nested.include({ 'a.b[1]': 'y' });
+ });
+
+ describe('docs example 4', () => {
+ expect({ '.a': { '[b]': 'x' } }).to.have.nested.property('\\.a.\\[b\\]');
+ expect({ '.a': { '[b]': 'x' } }).to.nested.include({ '\\.a.\\[b\\]': 'x' });
+ });
+
+ describe('docs example 5', () => {
+ Object.prototype.b = 2;
+
+ expect({ a: 1 }).to.have.own.property('a');
+ expect({ a: 1 }).to.have.property('b');
+ expect({ a: 1 }).to.not.have.own.property('b');
+
+ expect({ a: 1 }).to.own.include({ a: 1 });
+ expect({ a: 1 }).to.include({ b: 2 }).but.not.own.include({ b: 2 });
+ });
+
+ describe('docs example 6', () => {
+ expect([1, 2]).to.have.ordered.members([1, 2]).but.not.have.ordered.members([2, 1]);
+ });
+
+ describe('docs example 7', () => {
+ expect([1, 2, 3]).to.include.ordered.members([1, 2]).but.not.include.ordered.members([2, 3]);
+ });
+
+ describe('docs example 8', () => {
+ expect({ a: 1, b: 2 }).to.not.have.any.keys('c', 'd');
+ });
+
+ describe('docs example 9', () => {
+ expect({ a: 1, b: 2 }).to.have.all.keys('a', 'b');
+ });
+
+ describe('docs example 10', () => {
+ expect('foo').to.be.a('string');
+ expect({ a: 1 }).to.be.an('object');
+ expect(null).to.be.a('null');
+ expect(undefined).to.be.an('undefined');
+ expect(new Error()).to.be.an('error');
+ expect(Promise.resolve()).to.be.a('promise');
+ expect(new Float32Array()).to.be.a('float32array');
+ expect(Symbol()).to.be.a('symbol');
+ });
+
+ describe('docs example 11', () => {
+ var myObj = {
+ [Symbol.toStringTag]: 'myCustomType',
+ };
+
+ expect(myObj).to.be.a('myCustomType').but.not.an('object');
+ });
+
+ describe('docs example 12', () => {
+ expect([1, 2, 3]).to.be.an('array').that.includes(2);
+ expect([]).to.be.an('array').that.is.empty;
+ });
+
+ describe('docs example 13', () => {
+ expect('foo').to.be.a('string'); // Recommended
+ expect('foo').to.not.be.an('array'); // Not recommended
+ });
+
+ describe('docs example 14', () => {
+ expect(1).to.be.a('string', 'nooo why fail??');
+ expect(1, 'nooo why fail??').to.be.a('string');
+ });
+
+ describe('docs example 15', () => {
+ expect({ b: 2 }).to.have.a.property('b');
+ });
+
+ describe('docs example 16', () => {
+ expect('foobar').to.include('foo');
+ });
+
+ describe('docs example 17', () => {
+ expect([1, 2, 3]).to.include(2);
+ });
+
+ describe('docs example 18', () => {
+ expect({ a: 1, b: 2, c: 3 }).to.include({ a: 1, b: 2 });
+ });
+
+ describe('docs example 19', () => {
+ expect(new Set([1, 2])).to.include(2);
+ });
+
+ describe('docs example 20', () => {
+ expect(
+ new Map([
+ ['a', 1],
+ ['b', 2],
+ ])
+ ).to.include(2);
+ });
+
+ describe('docs example 21', () => {
+ expect([1, 2, 3]).to.be.an('array').that.includes(2);
+ });
+
+ describe('docs example 22', () => {
+ // Target array deeply (but not strictly) includes `{a: 1}`
+ expect([{ a: 1 }]).to.deep.include({ a: 1 });
+ expect([{ a: 1 }]).to.not.include({ a: 1 });
+
+ // Target object deeply (but not strictly) includes `x: {a: 1}`
+ expect({ x: { a: 1 } }).to.deep.include({ x: { a: 1 } });
+ expect({ x: { a: 1 } }).to.not.include({ x: { a: 1 } });
+ });
+
+ describe('docs example 23', () => {
+ Object.prototype.b = 2;
+
+ expect({ a: 1 }).to.own.include({ a: 1 });
+ expect({ a: 1 }).to.include({ b: 2 }).but.not.own.include({ b: 2 });
+ });
+
+ describe('docs example 24', () => {
+ expect({ a: { b: 2 } }).to.deep.own.include({ a: { b: 2 } });
+ });
+
+ describe('docs example 25', () => {
+ expect({ a: { b: ['x', 'y'] } }).to.nested.include({ 'a.b[1]': 'y' });
+ });
+
+ describe('docs example 26', () => {
+ expect({ '.a': { '[b]': 2 } }).to.nested.include({ '\\.a.\\[b\\]': 2 });
+ });
+
+ describe('docs example 27', () => {
+ expect({ a: { b: [{ c: 3 }] } }).to.deep.nested.include({ 'a.b[0]': { c: 3 } });
+ });
+
+ describe('docs example 28', () => {
+ expect('foobar').to.not.include('taco');
+ expect([1, 2, 3]).to.not.include(4);
+ });
+
+ describe('docs example 29', () => {
+ expect({ c: 3 }).to.not.have.any.keys('a', 'b'); // Recommended
+ expect({ c: 3 }).to.not.include({ a: 1, b: 2 }); // Not recommended
+ });
+
+ describe('docs example 30', () => {
+ expect({ a: 3, b: 4 }).to.include({ a: 3, b: 4 }); // Recommended
+ expect({ a: 3, b: 4 }).to.not.include({ a: 1, b: 2 }); // Not recommended
+ });
+
+ describe('docs example 31', () => {
+ expect([1, 2, 3]).to.include(4, 'nooo why fail??');
+ expect([1, 2, 3], 'nooo why fail??').to.include(4);
+ });
+
+ describe('docs example 32', () => {
+ // Target object's keys are a superset of ['a', 'b'] but not identical
+ expect({ a: 1, b: 2, c: 3 }).to.include.all.keys('a', 'b');
+ expect({ a: 1, b: 2, c: 3 }).to.not.have.all.keys('a', 'b');
+
+ // Target array is a superset of [1, 2] but not identical
+ expect([1, 2, 3]).to.include.members([1, 2]);
+ expect([1, 2, 3]).to.not.have.members([1, 2]);
+
+ // Duplicates in the subset are ignored
+ expect([1, 2, 3]).to.include.members([1, 2, 2, 2]);
+ });
+
+ describe('docs example 33', () => {
+ // Both assertions are identical
+ expect({ a: 1 }).to.include.any.keys('a', 'b');
+ expect({ a: 1 }).to.have.any.keys('a', 'b');
+ });
+
+ describe('docs example 34', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.be.ok; // Not recommended
+
+ expect(true).to.be.true; // Recommended
+ expect(true).to.be.ok; // Not recommended
+ });
+
+ describe('docs example 35', () => {
+ expect(0).to.equal(0); // Recommended
+ expect(0).to.not.be.ok; // Not recommended
+
+ expect(false).to.be.false; // Recommended
+ expect(false).to.not.be.ok; // Not recommended
+
+ expect(null).to.be.null; // Recommended
+ expect(null).to.not.be.ok; // Not recommended
+
+ expect(undefined).to.be.undefined; // Recommended
+ expect(undefined).to.not.be.ok; // Not recommended
+ });
+
+ describe('docs example 36', () => {
+ expect(false, 'nooo why fail??').to.be.ok;
+ });
+
+ describe('docs example 37', () => {
+ expect(true).to.be.true;
+ });
+
+ describe('docs example 38', () => {
+ expect(false).to.be.false; // Recommended
+ expect(false).to.not.be.true; // Not recommended
+
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.be.true; // Not recommended
+ });
+
+ describe('docs example 39', () => {
+ expect(false, 'nooo why fail??').to.be.true;
+ });
+
+ describe('docs example 40', () => {
+ expect(false).to.be.false;
+ });
+
+ describe('docs example 41', () => {
+ expect(true).to.be.true; // Recommended
+ expect(true).to.not.be.false; // Not recommended
+
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.be.false; // Not recommended
+ });
+
+ describe('docs example 42', () => {
+ expect(true, 'nooo why fail??').to.be.false;
+ });
+
+ describe('docs example 43', () => {
+ expect(null).to.be.null;
+ });
+
+ describe('docs example 44', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.be.null; // Not recommended
+ });
+
+ describe('docs example 45', () => {
+ expect(42, 'nooo why fail??').to.be.null;
+ });
+
+ describe('docs example 46', () => {
+ expect(undefined).to.be.undefined;
+ });
+
+ describe('docs example 47', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.be.undefined; // Not recommended
+ });
+
+ describe('docs example 48', () => {
+ expect(42, 'nooo why fail??').to.be.undefined;
+ });
+
+ describe('docs example 49', () => {
+ expect(NaN).to.be.NaN;
+ });
+
+ describe('docs example 50', () => {
+ expect('foo').to.equal('foo'); // Recommended
+ expect('foo').to.not.be.NaN; // Not recommended
+ });
+
+ describe('docs example 51', () => {
+ expect(42, 'nooo why fail??').to.be.NaN;
+ });
+
+ describe('docs example 52', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.exist; // Not recommended
+
+ expect(0).to.equal(0); // Recommended
+ expect(0).to.exist; // Not recommended
+ });
+
+ describe('docs example 53', () => {
+ expect(null).to.be.null; // Recommended
+ expect(null).to.not.exist; // Not recommended
+
+ expect(undefined).to.be.undefined; // Recommended
+ expect(undefined).to.not.exist; // Not recommended
+ });
+
+ describe('docs example 54', () => {
+ expect(null, 'nooo why fail??').to.exist;
+ });
+
+ describe('docs example 55', () => {
+ expect([]).to.be.empty;
+ expect('').to.be.empty;
+ });
+
+ describe('docs example 56', () => {
+ expect(new Set()).to.be.empty;
+ expect(new Map()).to.be.empty;
+ });
+
+ describe('docs example 57', () => {
+ expect({}).to.be.empty;
+ });
+
+ describe('docs example 58', () => {
+ expect([]).to.be.an('array').that.is.empty;
+ });
+
+ describe('docs example 59', () => {
+ expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
+ expect([1, 2, 3]).to.not.be.empty; // Not recommended
+
+ expect(new Set([1, 2, 3])).to.have.property('size', 3); // Recommended
+ expect(new Set([1, 2, 3])).to.not.be.empty; // Not recommended
+
+ expect(Object.keys({ a: 1 })).to.have.lengthOf(1); // Recommended
+ expect({ a: 1 }).to.not.be.empty; // Not recommended
+ });
+
+ describe('docs example 60', () => {
+ expect([1, 2, 3], 'nooo why fail??').to.be.empty;
+ });
+
+ describe('docs example 61', () => {
+ function test() {
+ expect(arguments).to.be.arguments;
+ }
+
+ test();
+ });
+
+ describe('docs example 62', () => {
+ expect('foo').to.be.a('string'); // Recommended
+ expect('foo').to.not.be.arguments; // Not recommended
+ });
+
+ describe('docs example 63', () => {
+ expect({}, 'nooo why fail??').to.be.arguments;
+ });
+
+ describe('docs example 64', () => {
+ expect(1).to.equal(1);
+ expect('foo').to.equal('foo');
+ });
+
+ describe('docs example 65', () => {
+ // Target object deeply (but not strictly) equals `{a: 1}`
+ expect({ a: 1 }).to.deep.equal({ a: 1 });
+ expect({ a: 1 }).to.not.equal({ a: 1 });
+
+ // Target array deeply (but not strictly) equals `[1, 2]`
+ expect([1, 2]).to.deep.equal([1, 2]);
+ expect([1, 2]).to.not.equal([1, 2]);
+ });
+
+ describe('docs example 66', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.equal(2); // Not recommended
+ });
+
+ describe('docs example 67', () => {
+ expect(1).to.equal(2, 'nooo why fail??');
+ expect(1, 'nooo why fail??').to.equal(2);
+ });
+
+ describe('docs example 68', () => {
+ // Target object is deeply (but not strictly) equal to {a: 1}
+ expect({ a: 1 }).to.eql({ a: 1 }).but.not.equal({ a: 1 });
+
+ // Target array is deeply (but not strictly) equal to [1, 2]
+ expect([1, 2]).to.eql([1, 2]).but.not.equal([1, 2]);
+ });
+
+ describe('docs example 69', () => {
+ expect({ a: 1 }).to.eql({ a: 1 }); // Recommended
+ expect({ a: 1 }).to.not.eql({ b: 2 }); // Not recommended
+ });
+
+ describe('docs example 70', () => {
+ expect({ a: 1 }).to.eql({ b: 2 }, 'nooo why fail??');
+ expect({ a: 1 }, 'nooo why fail??').to.eql({ b: 2 });
+ });
+
+ describe('docs example 71', () => {
+ expect(2).to.equal(2); // Recommended
+ expect(2).to.be.above(1); // Not recommended
+ });
+
+ describe('docs example 72', () => {
+ expect('foo').to.have.lengthOf(3); // Recommended
+ expect('foo').to.have.lengthOf.above(2); // Not recommended
+
+ expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
+ expect([1, 2, 3]).to.have.lengthOf.above(2); // Not recommended
+ });
+
+ describe('docs example 73', () => {
+ expect(2).to.equal(2); // Recommended
+ expect(1).to.not.be.above(2); // Not recommended
+ });
+
+ describe('docs example 74', () => {
+ expect(1).to.be.above(2, 'nooo why fail??');
+ expect(1, 'nooo why fail??').to.be.above(2);
+ });
+
+ describe('docs example 75', () => {
+ expect(2).to.equal(2); // Recommended
+ expect(2).to.be.at.least(1); // Not recommended
+ expect(2).to.be.at.least(2); // Not recommended
+ });
+
+ describe('docs example 76', () => {
+ expect('foo').to.have.lengthOf(3); // Recommended
+ expect('foo').to.have.lengthOf.at.least(2); // Not recommended
+
+ expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
+ expect([1, 2, 3]).to.have.lengthOf.at.least(2); // Not recommended
+ });
+
+ describe('docs example 77', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.be.at.least(2); // Not recommended
+ });
+
+ describe('docs example 78', () => {
+ expect(1).to.be.at.least(2, 'nooo why fail??');
+ expect(1, 'nooo why fail??').to.be.at.least(2);
+ });
+
+ describe('docs example 79', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.be.below(2); // Not recommended
+ });
+
+ describe('docs example 80', () => {
+ expect('foo').to.have.lengthOf(3); // Recommended
+ expect('foo').to.have.lengthOf.below(4); // Not recommended
+
+ expect([1, 2, 3]).to.have.length(3); // Recommended
+ expect([1, 2, 3]).to.have.lengthOf.below(4); // Not recommended
+ });
+
+ describe('docs example 81', () => {
+ expect(2).to.equal(2); // Recommended
+ expect(2).to.not.be.below(1); // Not recommended
+ });
+
+ describe('docs example 82', () => {
+ expect(2).to.be.below(1, 'nooo why fail??');
+ expect(2, 'nooo why fail??').to.be.below(1);
+ });
+
+ describe('docs example 83', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.be.at.most(2); // Not recommended
+ expect(1).to.be.at.most(1); // Not recommended
+ });
+
+ describe('docs example 84', () => {
+ expect('foo').to.have.lengthOf(3); // Recommended
+ expect('foo').to.have.lengthOf.at.most(4); // Not recommended
+
+ expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
+ expect([1, 2, 3]).to.have.lengthOf.at.most(4); // Not recommended
+ });
+
+ describe('docs example 85', () => {
+ expect(2).to.equal(2); // Recommended
+ expect(2).to.not.be.at.most(1); // Not recommended
+ });
+
+ describe('docs example 86', () => {
+ expect(2).to.be.at.most(1, 'nooo why fail??');
+ expect(2, 'nooo why fail??').to.be.at.most(1);
+ });
+
+ describe('docs example 87', () => {
+ expect(2).to.equal(2); // Recommended
+ expect(2).to.be.within(1, 3); // Not recommended
+ expect(2).to.be.within(2, 3); // Not recommended
+ expect(2).to.be.within(1, 2); // Not recommended
+ });
+
+ describe('docs example 88', () => {
+ expect('foo').to.have.lengthOf.within(2, 4); // Not recommended
+
+ expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
+ expect([1, 2, 3]).to.have.lengthOf.within(2, 4); // Not recommended
+ });
+
+ describe('docs example 89', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.be.within(2, 4); // Not recommended
+ });
+
+ describe('docs example 90', () => {
+ expect(4).to.be.within(1, 3, 'nooo why fail??');
+ expect(4, 'nooo why fail??').to.be.within(1, 3);
+ });
+
+ describe('docs example 91', () => {
+ function Cat() {}
+
+ expect(new Cat()).to.be.an.instanceof(Cat);
+ expect([1, 2]).to.be.an.instanceof(Array);
+ });
+
+ describe('docs example 92', () => {
+ expect({ a: 1 }).to.not.be.an.instanceof(Array);
+ });
+
+ describe('docs example 93', () => {
+ expect(1).to.be.an.instanceof(Array, 'nooo why fail??');
+ expect(1, 'nooo why fail??').to.be.an.instanceof(Array);
+ });
+
+ describe('docs example 94', () => {
+ expect({ a: 1 }).to.have.property('a');
+ });
+
+ describe('docs example 95', () => {
+ expect({ a: 1 }).to.have.property('a', 1);
+ });
+
+ describe('docs example 96', () => {
+ // Target object deeply (but not strictly) has property `x: {a: 1}`
+ expect({ x: { a: 1 } }).to.have.deep.property('x', { a: 1 });
+ expect({ x: { a: 1 } }).to.not.have.property('x', { a: 1 });
+ });
+
+ describe('docs example 97', () => {
+ Object.prototype.b = 2;
+
+ expect({ a: 1 }).to.have.own.property('a');
+ expect({ a: 1 }).to.have.own.property('a', 1);
+ expect({ a: 1 }).to.have.property('b');
+ expect({ a: 1 }).to.not.have.own.property('b');
+ });
+
+ describe('docs example 98', () => {
+ expect({ x: { a: 1 } }).to.have.deep.own.property('x', { a: 1 });
+ });
+
+ describe('docs example 99', () => {
+ expect({ a: { b: ['x', 'y'] } }).to.have.nested.property('a.b[1]');
+ expect({ a: { b: ['x', 'y'] } }).to.have.nested.property('a.b[1]', 'y');
+ });
+
+ describe('docs example 100', () => {
+ expect({ '.a': { '[b]': 'x' } }).to.have.nested.property('\\.a.\\[b\\]');
+ });
+
+ describe('docs example 101', () => {
+ expect({ a: { b: [{ c: 3 }] } }).to.have.deep.nested.property('a.b[0]', { c: 3 });
+ });
+
+ describe('docs example 102', () => {
+ expect({ a: 1 }).to.not.have.property('b');
+ });
+
+ describe('docs example 103', () => {
+ expect({ b: 2 }).to.not.have.property('a'); // Recommended
+ expect({ b: 2 }).to.not.have.property('a', 1); // Not recommended
+ });
+
+ describe('docs example 104', () => {
+ expect({ a: 3 }).to.have.property('a', 3); // Recommended
+ expect({ a: 3 }).to.not.have.property('a', 1); // Not recommended
+ });
+
+ describe('docs example 105', () => {
+ expect({ a: 1 }).to.have.property('a').that.is.a('number');
+ });
+
+ describe('docs example 106', () => {
+ // Recommended
+ expect({ a: 1 }).to.have.property('a', 2, 'nooo why fail??');
+ expect({ a: 1 }, 'nooo why fail??').to.have.property('a', 2);
+ expect({ a: 1 }, 'nooo why fail??').to.have.property('b');
+
+ // Not recommended
+ expect({ a: 1 }).to.have.property('b', undefined, 'nooo why fail??');
+ });
+
+ describe('docs example 107', () => {
+ expect({ a: 1 }).to.have.ownPropertyDescriptor('a');
+ });
+
+ describe('docs example 108', () => {
+ expect({ a: 1 }).to.have.ownPropertyDescriptor('a', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: 1,
+ });
+ });
+
+ describe('docs example 109', () => {
+ expect({ a: 1 }).to.not.have.ownPropertyDescriptor('b');
+ });
+
+ describe('docs example 110', () => {
+ // Recommended
+ expect({ b: 2 }).to.not.have.ownPropertyDescriptor('a');
+
+ // Not recommended
+ expect({ b: 2 }).to.not.have.ownPropertyDescriptor('a', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: 1,
+ });
+ });
+
+ describe('docs example 111', () => {
+ // Recommended
+ expect({ a: 3 }).to.have.ownPropertyDescriptor('a', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: 3,
+ });
+
+ // Not recommended
+ expect({ a: 3 }).to.not.have.ownPropertyDescriptor('a', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: 1,
+ });
+ });
+
+ describe('docs example 112', () => {
+ expect({ a: 1 }).to.have.ownPropertyDescriptor('a').that.has.property('enumerable', true);
+ });
+
+ describe('docs example 113', () => {
+ // Recommended
+ expect({ a: 1 }).to.have.ownPropertyDescriptor(
+ 'a',
+ {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: 2,
+ },
+ 'nooo why fail??'
+ );
+
+ // Recommended
+ expect({ a: 1 }, 'nooo why fail??').to.have.ownPropertyDescriptor('a', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: 2,
+ });
+
+ // Recommended
+ expect({ a: 1 }, 'nooo why fail??').to.have.ownPropertyDescriptor('b');
+
+ // Not recommended
+ expect({ a: 1 }).to.have.ownPropertyDescriptor('b', undefined, 'nooo why fail??');
+ });
+
+ describe('docs example 114', () => {
+ expect([1, 2, 3]).to.have.lengthOf(3);
+ expect('foo').to.have.lengthOf(3);
+ expect(new Set([1, 2, 3])).to.have.lengthOf(3);
+ expect(
+ new Map([
+ ['a', 1],
+ ['b', 2],
+ ['c', 3],
+ ])
+ ).to.have.lengthOf(3);
+ });
+
+ describe('docs example 115', () => {
+ expect('foo').to.have.lengthOf(3); // Recommended
+ expect('foo').to.not.have.lengthOf(4); // Not recommended
+ });
+
+ describe('docs example 116', () => {
+ expect([1, 2, 3]).to.have.lengthOf(2, 'nooo why fail??');
+ expect([1, 2, 3], 'nooo why fail??').to.have.lengthOf(2);
+ });
+
+ describe('docs example 117', () => {
+ // Recommended
+ expect([1, 2, 3]).to.have.lengthOf(3);
+
+ // Not recommended
+ expect([1, 2, 3]).to.have.lengthOf.above(2);
+ expect([1, 2, 3]).to.have.lengthOf.below(4);
+ expect([1, 2, 3]).to.have.lengthOf.at.least(3);
+ expect([1, 2, 3]).to.have.lengthOf.at.most(3);
+ expect([1, 2, 3]).to.have.lengthOf.within(2, 4);
+ });
+
+ describe('docs example 118', () => {
+ expect([1, 2, 3]).to.have.a.length(3); // incompatible; throws error
+ expect([1, 2, 3]).to.have.a.lengthOf(3); // passes as expected
+ });
+
+ describe('docs example 119', () => {
+ expect('foobar').to.match(/^foo/);
+ });
+
+ describe('docs example 120', () => {
+ expect('foobar').to.not.match(/taco/);
+ });
+
+ describe('docs example 121', () => {
+ expect('foobar').to.match(/taco/, 'nooo why fail??');
+ expect('foobar', 'nooo why fail??').to.match(/taco/);
+ });
+
+ describe('docs example 122', () => {
+ expect('foobar').to.have.string('bar');
+ });
+
+ describe('docs example 123', () => {
+ expect('foobar').to.not.have.string('taco');
+ });
+
+ describe('docs example 124', () => {
+ expect('foobar').to.have.string('taco', 'nooo why fail??');
+ expect('foobar', 'nooo why fail??').to.have.string('taco');
+ });
+
+ describe('docs example 125', () => {
+ expect({ a: 1, b: 2 }).to.have.all.keys('a', 'b');
+ expect(['x', 'y']).to.have.all.keys(0, 1);
+
+ expect({ a: 1, b: 2 }).to.have.all.keys(['a', 'b']);
+ expect(['x', 'y']).to.have.all.keys([0, 1]);
+
+ expect({ a: 1, b: 2 }).to.have.all.keys({ a: 4, b: 5 }); // ignore 4 and 5
+ expect(['x', 'y']).to.have.all.keys({ 0: 4, 1: 5 }); // ignore 4 and 5
+ });
+
+ describe('docs example 126', () => {
+ expect(
+ new Map([
+ ['a', 1],
+ ['b', 2],
+ ])
+ ).to.have.all.keys('a', 'b');
+ expect(new Set(['a', 'b'])).to.have.all.keys('a', 'b');
+ });
+
+ describe('docs example 127', () => {
+ expect({ a: 1, b: 2 }).to.be.an('object').that.has.all.keys('a', 'b');
+ });
+
+ describe('docs example 128', () => {
+ // Target set deeply (but not strictly) has key `{a: 1}`
+ expect(new Set([{ a: 1 }])).to.have.all.deep.keys([{ a: 1 }]);
+ expect(new Set([{ a: 1 }])).to.not.have.all.keys([{ a: 1 }]);
+ });
+
+ describe('docs example 129', () => {
+ // Recommended; asserts that target doesn't have any of the given keys
+ expect({ a: 1, b: 2 }).to.not.have.any.keys('c', 'd');
+
+ // Not recommended; asserts that target doesn't have all of the given
+ // keys but may or may not have some of them
+ expect({ a: 1, b: 2 }).to.not.have.all.keys('c', 'd');
+ });
+
+ describe('docs example 130', () => {
+ // Recommended; asserts that target has all the given keys
+ expect({ a: 1, b: 2 }).to.have.all.keys('a', 'b');
+
+ // Not recommended; asserts that target has at least one of the given
+ // keys but may or may not have more of them
+ expect({ a: 1, b: 2 }).to.have.any.keys('a', 'b');
+ });
+
+ describe('docs example 131', () => {
+ // Both assertions are identical
+ expect({ a: 1, b: 2 }).to.have.all.keys('a', 'b'); // Recommended
+ expect({ a: 1, b: 2 }).to.have.keys('a', 'b'); // Not recommended
+ });
+
+ describe('docs example 132', () => {
+ // Target object's keys are a superset of ['a', 'b'] but not identical
+ expect({ a: 1, b: 2, c: 3 }).to.include.all.keys('a', 'b');
+ expect({ a: 1, b: 2, c: 3 }).to.not.have.all.keys('a', 'b');
+ });
+
+ describe('docs example 133', () => {
+ // Both assertions are identical
+ expect({ a: 1 }).to.have.any.keys('a', 'b');
+ expect({ a: 1 }).to.include.any.keys('a', 'b');
+ });
+
+ describe('docs example 134', () => {
+ expect({ a: 1 }, 'nooo why fail??').to.have.key('b');
+ });
+
+ describe('docs example 135', () => {
+ var badFn = function () {
+ throw new TypeError('Illegal salmon!');
+ };
+
+ expect(badFn).to.throw();
+ });
+
+ describe('docs example 136', () => {
+ var badFn = function () {
+ throw new TypeError('Illegal salmon!');
+ };
+
+ expect(badFn).to.throw(TypeError);
+ });
+
+ describe('docs example 137', () => {
+ var err = new TypeError('Illegal salmon!');
+ var badFn = function () {
+ throw err;
+ };
+
+ expect(badFn).to.throw(err);
+ });
+
+ describe('docs example 138', () => {
+ var badFn = function () {
+ throw new TypeError('Illegal salmon!');
+ };
+
+ expect(badFn).to.throw('salmon');
+ });
+
+ describe('docs example 139', () => {
+ var badFn = function () {
+ throw new TypeError('Illegal salmon!');
+ };
+
+ expect(badFn).to.throw(/salmon/);
+ });
+
+ describe('docs example 140', () => {
+ var err = new TypeError('Illegal salmon!');
+ var badFn = function () {
+ throw err;
+ };
+
+ expect(badFn).to.throw(TypeError, 'salmon');
+ expect(badFn).to.throw(TypeError, /salmon/);
+ expect(badFn).to.throw(err, 'salmon');
+ expect(badFn).to.throw(err, /salmon/);
+ });
+
+ describe('docs example 141', () => {
+ var goodFn = function () {};
+
+ expect(goodFn).to.not.throw();
+ });
+
+ describe('docs example 142', () => {
+ var goodFn = function () {};
+
+ expect(goodFn).to.not.throw(); // Recommended
+ expect(goodFn).to.not.throw(ReferenceError, 'x'); // Not recommended
+ });
+
+ describe('docs example 143', () => {
+ var badFn = function () {
+ throw new TypeError('Illegal salmon!');
+ };
+
+ expect(badFn).to.throw(TypeError, 'salmon'); // Recommended
+ expect(badFn).to.not.throw(ReferenceError, 'x'); // Not recommended
+ });
+
+ describe('docs example 144', () => {
+ var err = new TypeError('Illegal salmon!');
+ err.code = 42;
+ var badFn = function () {
+ throw err;
+ };
+
+ expect(badFn).to.throw(TypeError).with.property('code', 42);
+ });
+
+ describe('docs example 145', () => {
+ var goodFn = function () {};
+
+ expect(goodFn).to.throw(TypeError, 'x', 'nooo why fail??');
+ expect(goodFn, 'nooo why fail??').to.throw();
+ });
+
+ describe('docs example 146', () => {
+ var fn = function () {
+ throw new TypeError('Illegal salmon!');
+ };
+
+ expect(fn).to.throw(); // Good! Tests `fn` as desired
+ expect(fn()).to.throw(); // Bad! Tests result of `fn()`, not `fn`
+ });
+
+ describe('docs example 147', () => {
+ expect(function () {
+ fn(42);
+ }).to.throw(); // Function expression
+ expect(() => fn(42)).to.throw(); // ES6 arrow function
+ });
+
+ describe('docs example 148', () => {
+ expect(function () {
+ cat.meow();
+ }).to.throw(); // Function expression
+ expect(() => cat.meow()).to.throw(); // ES6 arrow function
+ expect(cat.meow.bind(cat)).to.throw(); // Bind
+ });
+
+ describe('docs example 149', () => {
+ function Cat() {}
+ Cat.prototype.meow = function () {};
+
+ expect(new Cat()).to.respondTo('meow');
+ });
+
+ describe('docs example 150', () => {
+ function Cat() {}
+ Cat.prototype.meow = function () {};
+
+ expect(Cat).to.respondTo('meow');
+ });
+
+ describe('docs example 151', () => {
+ function Cat() {}
+ Cat.prototype.meow = function () {};
+ Cat.hiss = function () {};
+
+ expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow');
+ });
+
+ describe('docs example 152', () => {
+ function Cat() {}
+ Cat.prototype.meow = function () {};
+
+ expect(new Cat()).to.be.an('object').that.respondsTo('meow');
+ });
+
+ describe('docs example 153', () => {
+ function Dog() {}
+ Dog.prototype.bark = function () {};
+
+ expect(new Dog()).to.not.respondTo('meow');
+ });
+
+ describe('docs example 154', () => {
+ expect({}).to.respondTo('meow', 'nooo why fail??');
+ expect({}, 'nooo why fail??').to.respondTo('meow');
+ });
+
+ describe('docs example 155', () => {
+ function Cat() {}
+ Cat.prototype.meow = function () {};
+ Cat.hiss = function () {};
+
+ expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow');
+ });
+
+ describe('docs example 156', () => {
+ expect(1).to.satisfy(function (num) {
+ return num > 0;
+ });
+ });
+
+ describe('docs example 157', () => {
+ expect(1).to.not.satisfy(function (num) {
+ return num > 2;
+ });
+ });
+
+ describe('docs example 158', () => {
+ expect(1).to.satisfy(function (num) {
+ return num > 2;
+ }, 'nooo why fail??');
+
+ expect(1, 'nooo why fail??').to.satisfy(function (num) {
+ return num > 2;
+ });
+ });
+
+ describe('docs example 159', () => {
+ // Recommended
+ expect(1.5).to.equal(1.5);
+
+ // Not recommended
+ expect(1.5).to.be.closeTo(1, 0.5);
+ expect(1.5).to.be.closeTo(2, 0.5);
+ expect(1.5).to.be.closeTo(1, 1);
+ });
+
+ describe('docs example 160', () => {
+ expect(1.5).to.equal(1.5); // Recommended
+ expect(1.5).to.not.be.closeTo(3, 1); // Not recommended
+ });
+
+ describe('docs example 161', () => {
+ expect(1.5).to.be.closeTo(3, 1, 'nooo why fail??');
+ expect(1.5, 'nooo why fail??').to.be.closeTo(3, 1);
+ });
+
+ describe('docs example 162', () => {
+ expect([1, 2, 3]).to.have.members([2, 1, 3]);
+ expect([1, 2, 2]).to.have.members([2, 1, 2]);
+ });
+
+ describe('docs example 163', () => {
+ // Target array deeply (but not strictly) has member `{a: 1}`
+ expect([{ a: 1 }]).to.have.deep.members([{ a: 1 }]);
+ expect([{ a: 1 }]).to.not.have.members([{ a: 1 }]);
+ });
+
+ describe('docs example 164', () => {
+ expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]);
+ expect([1, 2, 3]).to.have.members([2, 1, 3]).but.not.ordered.members([2, 1, 3]);
+ });
+
+ describe('docs example 165', () => {
+ // Target array is a superset of [1, 2] but not identical
+ expect([1, 2, 3]).to.include.members([1, 2]);
+ expect([1, 2, 3]).to.not.have.members([1, 2]);
+
+ // Duplicates in the subset are ignored
+ expect([1, 2, 3]).to.include.members([1, 2, 2, 2]);
+ });
+
+ describe('docs example 166', () => {
+ expect([{ a: 1 }, { b: 2 }, { c: 3 }])
+ .to.include.deep.ordered.members([{ a: 1 }, { b: 2 }])
+ .but.not.include.deep.ordered.members([{ b: 2 }, { c: 3 }]);
+ });
+
+ describe('docs example 167', () => {
+ expect([1, 2]).to.not.include(3).and.not.include(4); // Recommended
+ expect([1, 2]).to.not.have.members([3, 4]); // Not recommended
+ });
+
+ describe('docs example 168', () => {
+ expect([1, 2]).to.have.members([1, 2, 3], 'nooo why fail??');
+ expect([1, 2], 'nooo why fail??').to.have.members([1, 2, 3]);
+ });
+
+ describe('docs example 169', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.be.oneOf([1, 2, 3]); // Not recommended
+ });
+
+ describe('docs example 170', () => {
+ expect(1).to.equal(1); // Recommended
+ expect(1).to.not.be.oneOf([2, 3, 4]); // Not recommended
+ });
+
+ describe('docs example 171', () => {
+ expect('Today is sunny').to.contain.oneOf(['sunny', 'cloudy']);
+ expect('Today is rainy').to.not.contain.oneOf(['sunny', 'cloudy']);
+ expect([1, 2, 3]).to.contain.oneOf([3, 4, 5]);
+ expect([1, 2, 3]).to.not.contain.oneOf([4, 5, 6]);
+ });
+
+ describe('docs example 172', () => {
+ expect(1).to.be.oneOf([2, 3, 4], 'nooo why fail??');
+ expect(1, 'nooo why fail??').to.be.oneOf([2, 3, 4]);
+ });
+
+ describe('docs example 173', () => {
+ var dots = '',
+ addDot = function () {
+ dots += '.';
+ },
+ getDots = function () {
+ return dots;
+ };
+
+ // Recommended
+ expect(getDots()).to.equal('');
+ addDot();
+ expect(getDots()).to.equal('.');
+
+ // Not recommended
+ expect(addDot).to.change(getDots);
+ });
+
+ describe('docs example 174', () => {
+ var myObj = { dots: '' },
+ addDot = function () {
+ myObj.dots += '.';
+ };
+
+ // Recommended
+ expect(myObj).to.have.property('dots', '');
+ addDot();
+ expect(myObj).to.have.property('dots', '.');
+
+ // Not recommended
+ expect(addDot).to.change(myObj, 'dots');
+ });
+
+ describe('docs example 175', () => {
+ var dots = '',
+ noop = function () {},
+ getDots = function () {
+ return dots;
+ };
+
+ expect(noop).to.not.change(getDots);
+
+ var myObj = { dots: '' },
+ noop = function () {};
+
+ expect(noop).to.not.change(myObj, 'dots');
+ });
+
+ describe('docs example 176', () => {
+ var myObj = { dots: '' },
+ addDot = function () {
+ myObj.dots += '.';
+ };
+
+ expect(addDot).to.not.change(myObj, 'dots', 'nooo why fail??');
+
+ var dots = '',
+ addDot = function () {
+ dots += '.';
+ },
+ getDots = function () {
+ return dots;
+ };
+
+ expect(addDot, 'nooo why fail??').to.not.change(getDots);
+ });
+
+ describe('docs example 177', () => {
+ var myObj = { val: 1 },
+ addTwo = function () {
+ myObj.val += 2;
+ },
+ subtractTwo = function () {
+ myObj.val -= 2;
+ };
+
+ expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
+ expect(addTwo).to.change(myObj, 'val').by(2); // Not recommended
+
+ expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
+ expect(subtractTwo).to.change(myObj, 'val').by(2); // Not recommended
+ });
+
+ describe('docs example 178', () => {
+ var val = 1,
+ addTwo = function () {
+ val += 2;
+ },
+ getVal = function () {
+ return val;
+ };
+
+ expect(addTwo).to.increase(getVal).by(2); // Recommended
+ expect(addTwo).to.increase(getVal); // Not recommended
+ });
+
+ describe('docs example 179', () => {
+ var myObj = { val: 1 },
+ addTwo = function () {
+ myObj.val += 2;
+ };
+
+ expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
+ expect(addTwo).to.increase(myObj, 'val'); // Not recommended
+ });
+
+ describe('docs example 180', () => {
+ var myObj = { val: 1 },
+ subtractTwo = function () {
+ myObj.val -= 2;
+ };
+
+ expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
+ expect(subtractTwo).to.not.increase(myObj, 'val'); // Not recommended
+ });
+
+ describe('docs example 181', () => {
+ var myObj = { val: 1 },
+ noop = function () {};
+
+ expect(noop).to.not.change(myObj, 'val'); // Recommended
+ expect(noop).to.not.increase(myObj, 'val'); // Not recommended
+ });
+
+ describe('docs example 182', () => {
+ var myObj = { val: 1 },
+ noop = function () {};
+
+ expect(noop).to.increase(myObj, 'val', 'nooo why fail??');
+
+ var val = 1,
+ noop = function () {},
+ getVal = function () {
+ return val;
+ };
+
+ expect(noop, 'nooo why fail??').to.increase(getVal);
+ });
+
+ describe('docs example 183', () => {
+ var val = 1,
+ subtractTwo = function () {
+ val -= 2;
+ },
+ getVal = function () {
+ return val;
+ };
+
+ expect(subtractTwo).to.decrease(getVal).by(2); // Recommended
+ expect(subtractTwo).to.decrease(getVal); // Not recommended
+ });
+
+ describe('docs example 184', () => {
+ var myObj = { val: 1 },
+ subtractTwo = function () {
+ myObj.val -= 2;
+ };
+
+ expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
+ expect(subtractTwo).to.decrease(myObj, 'val'); // Not recommended
+ });
+
+ describe('docs example 185', () => {
+ var myObj = { val: 1 },
+ addTwo = function () {
+ myObj.val += 2;
+ };
+
+ expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
+ expect(addTwo).to.not.decrease(myObj, 'val'); // Not recommended
+ });
+
+ describe('docs example 186', () => {
+ var myObj = { val: 1 },
+ noop = function () {};
+
+ expect(noop).to.not.change(myObj, 'val'); // Recommended
+ expect(noop).to.not.decrease(myObj, 'val'); // Not recommended
+ });
+
+ describe('docs example 187', () => {
+ var myObj = { val: 1 },
+ noop = function () {};
+
+ expect(noop).to.decrease(myObj, 'val', 'nooo why fail??');
+
+ var val = 1,
+ noop = function () {},
+ getVal = function () {
+ return val;
+ };
+
+ expect(noop, 'nooo why fail??').to.decrease(getVal);
+ });
+
+ describe('docs example 188', () => {
+ var myObj = { val: 1 },
+ addTwo = function () {
+ myObj.val += 2;
+ };
+
+ expect(addTwo).to.increase(myObj, 'val').by(2);
+ });
+
+ describe('docs example 189', () => {
+ var myObj = { val: 1 },
+ subtractTwo = function () {
+ myObj.val -= 2;
+ };
+
+ expect(subtractTwo).to.decrease(myObj, 'val').by(2);
+ });
+
+ describe('docs example 190', () => {
+ var myObj = { val: 1 },
+ addTwo = function () {
+ myObj.val += 2;
+ },
+ subtractTwo = function () {
+ myObj.val -= 2;
+ };
+
+ expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
+ expect(addTwo).to.change(myObj, 'val').by(2); // Not recommended
+
+ expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
+ expect(subtractTwo).to.change(myObj, 'val').by(2); // Not recommended
+ });
+
+ describe('docs example 191', () => {
+ var myObj = { val: 1 },
+ addTwo = function () {
+ myObj.val += 2;
+ };
+
+ // Recommended
+ expect(addTwo).to.increase(myObj, 'val').by(2);
+
+ // Not recommended
+ expect(addTwo).to.increase(myObj, 'val').but.not.by(3);
+ });
+
+ describe('docs example 192', () => {
+ var myObj = { val: 1 },
+ addTwo = function () {
+ myObj.val += 2;
+ };
+
+ expect(addTwo).to.increase(myObj, 'val').by(3, 'nooo why fail??');
+ expect(addTwo, 'nooo why fail??').to.increase(myObj, 'val').by(3);
+ });
+
+ describe('docs example 193', () => {
+ expect({ a: 1 }).to.be.extensible;
+ });
+
+ describe('docs example 194', () => {
+ var nonExtensibleObject = Object.preventExtensions({}),
+ sealedObject = Object.seal({}),
+ frozenObject = Object.freeze({});
+
+ expect(nonExtensibleObject).to.not.be.extensible;
+ expect(sealedObject).to.not.be.extensible;
+ expect(frozenObject).to.not.be.extensible;
+ expect(1).to.not.be.extensible;
+ });
+
+ describe('docs example 195', () => {
+ expect(1, 'nooo why fail??').to.be.extensible;
+ });
+
+ describe('docs example 196', () => {
+ var sealedObject = Object.seal({});
+ var frozenObject = Object.freeze({});
+
+ expect(sealedObject).to.be.sealed;
+ expect(frozenObject).to.be.sealed;
+ expect(1).to.be.sealed;
+ });
+
+ describe('docs example 197', () => {
+ expect({ a: 1 }).to.not.be.sealed;
+ });
+
+ describe('docs example 198', () => {
+ expect({ a: 1 }, 'nooo why fail??').to.be.sealed;
+ });
+
+ describe('docs example 199', () => {
+ var frozenObject = Object.freeze({});
+
+ expect(frozenObject).to.be.frozen;
+ expect(1).to.be.frozen;
+ });
+
+ describe('docs example 200', () => {
+ expect({ a: 1 }).to.not.be.frozen;
+ });
+
+ describe('docs example 201', () => {
+ expect({ a: 1 }, 'nooo why fail??').to.be.frozen;
+ });
+
+ describe('docs example 202', () => {
+ expect(1).to.be.finite;
+ });
+
+ describe('docs example 203', () => {
+ expect('foo').to.be.a('string'); // Recommended
+ expect('foo').to.not.be.finite; // Not recommended
+ });
+
+ describe('docs example 204', () => {
+ expect(NaN).to.be.NaN; // Recommended
+ expect(NaN).to.not.be.finite; // Not recommended
+ });
+
+ describe('docs example 205', () => {
+ expect(Infinity).to.equal(Infinity); // Recommended
+ expect(Infinity).to.not.be.finite; // Not recommended
+ });
+
+ describe('docs example 206', () => {
+ expect(-Infinity).to.equal(-Infinity); // Recommended
+ expect(-Infinity).to.not.be.finite; // Not recommended
+ });
+
+ describe('docs example 207', () => {
+ expect('foo', 'nooo why fail??').to.be.finite;
+ });
+
+ describe('docs example 208', () => {
+ expect.fail();
+ expect.fail('custom error message');
+ expect.fail(1, 2);
+ expect.fail(1, 2, 'custom error message');
+ expect.fail(1, 2, 'custom error message', '>');
+ expect.fail(1, 2, undefined, '>');
+ });
+
+ // describe('docs example 209', () => {
+ // should.fail();
+ // should.fail("custom error message");
+ // should.fail(1, 2);
+ // should.fail(1, 2, "custom error message");
+ // should.fail(1, 2, "custom error message", ">");
+ // should.fail(1, 2, undefined, ">");
+
+ // });
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/generating-uuids.md b/docs/sources/v0.50.x/examples/generating-uuids.md
new file mode 100644
index 000000000..9c736c9de
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/generating-uuids.md
@@ -0,0 +1,82 @@
+---
+title: 'Generating UUIDs'
+description: 'Scripting example on how to generate UUIDs in your load test.'
+weight: 11
+---
+
+# Generating UUIDs
+
+If you want to make a version 4 UUID,
+you can use the [`uuidv4` function](https://grafana.com/docs/k6//javascript-api/jslib/utils/uuidv4) from the [k6 JS lib repository](https://jslib.k6.io/).
+
+{{< code >}}
+
+```javascript
+import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
+
+export default function () {
+ const randomUUID = uuidv4();
+ console.log(randomUUID); // 35acae14-f7cb-468a-9866-1fc45713149a
+}
+```
+
+{{< /code >}}
+
+If you really need other UUID versions, you must rely on an external library.
+
+## Generate v1 UUIDs
+
+As k6 doesn't have built-in support
+for version 1 UUID, you'll have to use a third-party library.
+
+This example uses a Node.js library called [uuid](https://www.npmjs.com/package/uuid)
+and [Browserify](http://browserify.org/) (to make it work in k6).
+For this to work, we first need to go through a few required steps:
+
+1. Make sure you have the necessary prerequisites installed:
+ [Node.js](https://nodejs.org/en/download/) and [Browserify](http://browserify.org/)
+
+2. Install the `uuid` library:
+ {{< code >}}
+
+ ```bash
+ $ npm install uuid@3.4.0
+ ```
+
+ {{< /code >}}
+
+3. Run it through browserify:
+ {{< code >}}
+
+ ```bash
+ $ browserify node_modules/uuid/index.js -s uuid > uuid.js
+ ```
+
+ {{< /code >}}
+
+4. Move the `uuid.js` file to the same folder as your script file. Now you can import
+ it into your test script:
+
+ {{< code >}}
+
+ ```javascript
+ import uuid from './uuid.js';
+ ```
+
+ {{< /code >}}
+
+This example generates a v1 UUID:
+
+{{< code >}}
+
+```javascript
+import uuid from './uuid.js';
+
+export default function () {
+ // Generate a UUID v1
+ const uuid1 = uuid.v1();
+ console.log(uuid1);
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/get-started-with-k6/_index.md b/docs/sources/v0.50.x/examples/get-started-with-k6/_index.md
new file mode 100644
index 000000000..a99732943
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/get-started-with-k6/_index.md
@@ -0,0 +1,46 @@
+---
+title: 'Get started with k6'
+description: A series of docs to learn how to use the major features of k6
+weight: 01
+weight: -10
+---
+
+# Get started with k6
+
+This tutorial provides some procedures for common real-life uses of k6.
+They assume no prior knowledge of k6 or of JavaScript.
+
+These tasks are all reproducible, so open up your favorite editor and follow along.
+Imagine that you are a developer or tester who is responsible for performance in your development team.
+
+## Context: test a new endpoint
+
+Your development team has just developed a new login endpoint.
+Before releasing, you must test the endpoint for the following questions:
+
+- Does it work?
+- Does it perform within the service-level objectives under average load?
+- At what load does performance degrade beyond objectives?
+
+After you test the endpoint, your team wants you to compare different components of the user-facing application.
+
+Finally, after you test the API and web application, break your scripts down into reusable parts.
+
+## Before you start
+
+We encourage you to open your terminal and actively experiment with these examples.
+The tutorial requires the following:
+
+- [ ][k6 installed](https://grafana.com/docs/k6//get-started/installation)
+- [ ] A clean directory to experiment in.
+- [ ] Something to do during the minute or two when k6 runs the longest example tests
+- [ ] Optional: [`jq`](https://stedolan.github.io/jq/) to filter some results
+
+## Tutorials
+
+| In this tutorial | Learn how to |
+| ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
+| [Test for functional behavior](https://grafana.com/docs/k6//examples/get-started-with-k6/test-for-functional-behavior) | Use k6 to script requests and evaluate that performance is correct |
+| [Test for performance](https://grafana.com/docs/k6//examples/get-started-with-k6/test-for-performance) | Use k6 to increase load and find faults |
+| [Analyze results](https://grafana.com/docs/k6//examples/get-started-with-k6/analyze-results) | Filter results and make custom metrics |
+| [Reuse and re-run](https://grafana.com/docs/k6//examples/get-started-with-k6/reuse-and-re-run-tests) | Modularize and re-run tests |
diff --git a/docs/sources/v0.50.x/examples/get-started-with-k6/analyze-results.md b/docs/sources/v0.50.x/examples/get-started-with-k6/analyze-results.md
new file mode 100644
index 000000000..f447af313
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/get-started-with-k6/analyze-results.md
@@ -0,0 +1,357 @@
+---
+title: Analyze results
+description: Use k6 to write custom metrics and filter results.
+weight: 300
+---
+
+# Analyze results
+
+In this tutorial, learn how to:
+
+- Apply tags to filter specific results
+- Learn about k6 metrics
+- Use [jq](https://jqlang.github.io/jq/) to filter JSON results
+- Define groups to organize the test
+- Create custom metrics
+
+## Context: k6 result outputs
+
+k6 provides many [result outputs](https://grafana.com/docs/k6//results-output/).
+By default, the [end-of-test summary](https://grafana.com/docs/k6//results-output/end-of-test) provides the aggregated results of the test metrics.
+
+{{< code >}}
+
+```bash
+checks.........................: 50.00% ✓ 45 ✗ 45
+data_received..................: 1.3 MB 31 kB/s
+data_sent......................: 81 kB 2.0 kB/s
+group_duration.................: avg=6.45s min=4.01s med=6.78s max=10.15s p(90)=9.29s p(95)=9.32s
+http_req_blocked...............: avg=57.62ms min=7µs med=12.25µs max=1.35s p(90)=209.41ms p(95)=763.61ms
+http_req_connecting............: avg=20.51ms min=0s med=0s max=1.1s p(90)=100.76ms p(95)=173.41ms
+http_req_duration..............: avg=144.56ms min=104.11ms med=110.47ms max=1.14s p(90)=203.54ms p(95)=215.95ms
+ { expected_response:true }...: avg=144.56ms min=104.11ms med=110.47ms max=1.14s p(90)=203.54ms p(95)=215.95ms
+http_req_failed................: 0.00% ✓ 0 ✗ 180
+http_req_receiving.............: avg=663.96µs min=128.46µs med=759.82µs max=1.66ms p(90)=1.3ms p(95)=1.46ms
+http_req_sending...............: avg=88.01µs min=43.07µs med=78.03µs max=318.81µs p(90)=133.15µs p(95)=158.3µs
+http_req_tls_handshaking.......: avg=29.25ms min=0s med=0s max=458.71ms p(90)=108.31ms p(95)=222.46ms
+http_req_waiting...............: avg=143.8ms min=103.5ms med=109.5ms max=1.14s p(90)=203.19ms p(95)=215.56ms
+http_reqs......................: 180 4.36938/s
+iteration_duration.............: avg=12.91s min=12.53s med=12.77s max=14.35s p(90)=13.36s p(95)=13.37s
+iterations.....................: 45 1.092345/s
+vus............................: 1 min=1 max=19
+vus_max........................: 20 min=20 max=20
+```
+
+{{< /code >}}
+
+For simplicity to learn about [k6 metric results](https://grafana.com/docs/k6//using-k6/metrics/reference), this tutorial uses the [JSON output](https://grafana.com/docs/k6//results-output/real-time/json) and [jq](https://jqlang.github.io/jq/) to filter results.
+
+For other options to analyze test results such as storage and time-series visualizations in real-time, refer to:
+
+- [Results output](https://grafana.com/docs/k6//results-output/)
+
+- [Ways to visualize k6 results](https://k6.io/blog/ways-to-visualize-k6-results/)
+
+## Write time-series results to a JSON file
+
+To output results to a JSON file, use the `--out` flag.
+
+```bash
+k6 run --out json=results.json api-test.js
+```
+
+Then run this `jq` command to filter the latency results; `http_req_duration` metric.
+
+```bash
+jq '. | select(.type == "Point" and .metric == "http_req_duration")' results.json
+```
+
+k6 results have a number of [built-in tags](https://grafana.com/docs/k6//using-k6/tags-and-groups#system-tags). For example, filter results to only results where the status is 200.
+
+```bash
+jq '. | select(.type == "Point" and .data.tags.status == "200")' results.json
+```
+
+Or calculate the aggregated value of any metric with any particular tags.
+
+{{< code >}}
+
+```average
+jq '. | select(.type == "Point" and .metric == "http_req_duration") | .data.value' results.json | jq -s 'add/length'
+```
+
+```min
+jq '. | select(.type == "Point" and .metric == "http_req_duration") | .data.value' results.json | jq -s min
+```
+
+```max
+jq '. | select(.type == "Point" and .metric == "http_req_duration") | .data.value' results.json | jq -s max
+```
+
+{{< /code >}}
+
+## Apply custom tags
+
+You can also apply [_Tags_](https://grafana.com/docs/k6//using-k6/tags-and-groups#tags) to requests or code blocks. For example, this is how you can add a [`tags`](https://grafana.com/docs/k6//using-k6/tags-and-groups#tags) to the [request params](https://grafana.com/docs/k6//javascript-api/k6-http/params).
+
+```javascript
+const params = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ tags: {
+ 'my-custom-tag': 'auth-api',
+ },
+};
+```
+
+Create a new script named "tagged-login.js", and add a custom tag to it.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+
+export default function () {
+ const url = 'https://test-api.k6.io';
+ const payload = JSON.stringify({
+ username: 'test_case',
+ password: '1234',
+ });
+
+ const params = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ //apply tags
+ tags: {
+ 'my-custom-tag': 'auth-api',
+ },
+ };
+
+ //Login with tags
+ http.post(`${url}/auth/basic/login`, payload, params);
+}
+```
+
+{{< /code >}}
+
+Run the test:
+
+```bash
+k6 run --out json=results.json tagged-login.js
+```
+
+Filter the results for this custom tag:
+
+```bash
+jq '. | select(.type == "Point" and .metric == "http_req_duration" and .data.tags."my-custom-tag" == "auth-api")' results.json
+```
+
+## Organize requests in groups
+
+You can also organize your test logic into [Groups](https://grafana.com/docs/k6//using-k6/tags-and-groups#groups). Test logic inside a `group` tags all requests and metrics within its block.
+Groups can help you organize the test as a series of logical transactions or blocks.
+
+### Context: a new test to group test logic
+
+Results filtering isn't very meaningful in a test that makes one request.
+And the API test script is getting long.
+To learn more about how to compare results and other k6 APIs, write a test for the following situation:
+
+> **A dummy example**: your development team wants to evaluate the performance of two user-facing flows.
+>
+> - visit an endpoint, then another one
+> - A GET request to `https://test.k6.io/contacts.php`
+> - A GET to `https://test.k6.io/`
+> - play the coinflip game:
+> - A POST request to `https://test.k6.io/flip_coin.php` with the query param `bet=heads`
+> - Another POST to `https://test.k6.io/flip_coin.php` with the query param `bet=tails`
+
+Can you figure out how to [script the requests](https://grafana.com/docs/k6//using-k6/http-requests)?
+If not, use the following script. Since this example simulates a human user rather than an API call, it has a sleep between each request. Run with `k6 run multiple-flows.js`.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { group, sleep } from 'k6';
+
+//set URL as variable
+const baseUrl = 'https://test.k6.io';
+
+export default function () {
+ // visit contacts
+ http.get(`${baseUrl}/contacts.php`);
+ sleep(1);
+ // return to the home page
+ http.get(`${baseUrl}/`);
+ sleep(1);
+
+ //play coinflip game
+ http.get(`${baseUrl}/flip_coin.php?bet=heads`);
+ sleep(1);
+ http.get(`${baseUrl}/flip_coin.php?bet=tails`);
+ sleep(1);
+}
+```
+
+{{< /code >}}
+
+### Add Group functions
+
+Wrap the two endpoints in different groups.
+Name one group `Contacts flow` and another `Coinflip game`.
+
+{{< code >}}
+
+```javascript
+//import necessary modules
+import http from 'k6/http';
+import { group, sleep } from 'k6';
+
+//set baseURL
+const baseUrl = 'https://test.k6.io';
+
+export default function () {
+ // visit some endpoints in one group
+ group('Contacts flow', function () {
+ http.get(`${baseUrl}/contacts.php`);
+ sleep(1);
+ // return to the home page
+ http.get(`${baseUrl}/`);
+ sleep(1);
+ });
+
+ // Coinflip players in another group
+ group('Coinflip game', function () {
+ http.get(`${baseUrl}/flip_coin.php?bet=heads`);
+ sleep(1);
+ http.get(`${baseUrl}/flip_coin.php?bet=tails`);
+ sleep(1);
+ });
+}
+```
+
+{{< /code >}}
+
+### Run and filter
+
+Inspect the results for only the `Coinflip game` group.
+To do so:
+
+1. Save the preceding script as `multiple-flows.js`.
+1. Run the script with the command:
+
+```bash
+k6 run multiple-flows.js --out json=results.json --iterations 10
+```
+
+1. Inspect the results with `jq`. Group names have a `::` prefix.
+
+```bash
+jq '. | select(.data.tags.group == "::Coinflip game")' results.json
+```
+
+## Add a custom metric
+
+As you have seen in the output, all k6 tests emit metrics.
+However, if the built-in metrics aren't enough, you can [create custom metrics](https://grafana.com/docs/k6//using-k6/metrics/create-custom-metrics).
+A common use case is to collect metrics of a particular scope of your test.
+
+As an example, create a metric that collects latency results for each group:
+
+1. Import [`Trend`](https://grafana.com/docs/k6//javascript-api/k6-metrics/trend) from the k6 metrics module.
+1. Create two duration trend metric functions.
+1. In each group, add the `duration` time to the trend for requests to `contacts` and the `coin_flip` endpoints.
+
+{{< code >}}
+
+```javascript
+//import necessary modules
+import http from 'k6/http';
+import { group, sleep } from 'k6';
+import { Trend } from 'k6/metrics';
+
+//set baseURL
+const baseUrl = 'https://test.k6.io';
+
+// Create custom trend metrics
+const contactsLatency = new Trend('contacts_duration');
+const coinflipLatency = new Trend('coinflip_duration');
+
+export default function () {
+ // Put visits to contact page in one group
+ let res;
+ group('Contacts flow', function () {
+ // save response as variable
+ res = http.get(`${baseUrl}/contacts.php`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+
+ res = http.get(`${baseUrl}/`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+ });
+
+ // Coinflip players in another group
+
+ group('Coinflip game', function () {
+ // save response as variable
+ let res = http.get(`${baseUrl}/flip_coin.php?bet=heads`);
+ // add duration property to metric
+ coinflipLatency.add(res.timings.duration);
+ sleep(1);
+
+ res = http.get(`${baseUrl}/flip_coin.php?bet=tails`);
+ // add duration property to metric
+ coinflipLatency.add(res.timings.duration);
+ sleep(1);
+ });
+}
+```
+
+{{< /code >}}
+
+Run the test with small number of iterations and output the results to `results.json`.
+
+```bash
+k6 run multiple-flows.js --out json=results.json --iterations 10
+```
+
+Look for the custom trend metrics in the end-of-test console summary.
+
+```bash
+coinflip_duration..............: avg=119.6438 min=116.481 med=118.4755 max=135.498 p(90)=121.8459 p(95)=123.89565
+contacts_duration..............: avg=125.76985 min=116.973 med=120.6735 max=200.507 p(90)=127.9271 p(95)=153.87245
+```
+
+You can also query custom metric results from the JSON results. For example, to get the aggregated results as.
+
+{{< code >}}
+
+```avg
+jq '. | select(.type == "Point" and .metric == "coinflip_duration") | .data.value' results.json | jq -s 'add/length'
+```
+
+```min
+jq '. | select(.type == "Point" and .metric == "coinflip_duration") | .data.value' results.json | jq -s min
+```
+
+```max
+jq '. | select(.type == "Point" and .metric == "coinflip_duration") | .data.value' results.json | jq -s max
+```
+
+{{< /code >}}
+
+## Next steps
+
+In this tutorial, you looked at granular output and filtered by built-in and custom tags.
+Then you made a new script with groups.
+Finally, you added a new metric for each group.
+A next step would be to create a [Custom end-of-test summary](https://grafana.com/docs/k6//results-output/end-of-test/custom-summary) or to [stream the results to a database](https://grafana.com/docs/k6//results-output/real-time#service).
+
+For ongoing operations, you can modularize your logic and configuration.
+That's the subject of the [next step of this tutorial](https://grafana.com/docs/k6//examples/get-started-with-k6/reuse-and-re-run-tests).
diff --git a/docs/sources/v0.50.x/examples/get-started-with-k6/reuse-and-re-run-tests.md b/docs/sources/v0.50.x/examples/get-started-with-k6/reuse-and-re-run-tests.md
new file mode 100644
index 000000000..2b3b8506b
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/get-started-with-k6/reuse-and-re-run-tests.md
@@ -0,0 +1,418 @@
+---
+title: Reuse and re-run tests
+description: Modularize your k6 test logic and workload configuration.
+weight: 400
+---
+
+# Reuse and re-run tests
+
+In the previous tutorials, you designed k6 scripts to assert performance and make comparing results easy.
+
+In this tutorial, learn how to:
+
+- Modularize test scripts into reusable components
+- Dynamically configure scripts with environment variables
+
+## Example script
+
+For fun, let's combine the scripts from the previous tutorials.
+Use the logic of the `multiple-flows.js` test with the thresholds and scenario of the `api-test.js` (feel free to add more checks, requests, groups, and so on).
+Take note of the features of this script:
+
+- The `default` function has two groups, `Contacts flow`, and `Coinflip game`
+- The `options` object has two properties, `thresholds` and `scenarios`
+
+In the following sections, learn how to split these components into separate files, and combine them dynamically at run time.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { group, sleep } from 'k6';
+import { Trend } from 'k6/metrics';
+
+//define configuration
+export const options = {
+ scenarios: {
+ //arbitrary name of scenario:
+ breaking: {
+ executor: 'ramping-vus',
+ stages: [
+ { duration: '10s', target: 20 },
+ { duration: '50s', target: 20 },
+ { duration: '50s', target: 40 },
+ { duration: '50s', target: 60 },
+ { duration: '50s', target: 80 },
+ { duration: '50s', target: 100 },
+ { duration: '50s', target: 120 },
+ { duration: '50s', target: 140 },
+ //....
+ ],
+ },
+ },
+ //define thresholds
+ thresholds: {
+ http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // availability threshold for error rate
+ http_req_duration: ['p(99)<1000'], // Latency threshold for percentile
+ },
+};
+
+//set baseURL
+const baseUrl = 'https://test.k6.io';
+
+// Create custom trends
+const contactsLatency = new Trend('contacts_duration');
+const coinflipLatency = new Trend('coinflip_duration');
+
+export default function () {
+ // Put visits to contact page in one group
+ let res;
+ group('Contacts flow', function () {
+ // save response as variable
+ res = http.get(`${baseUrl}/contacts.php`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+
+ res = http.get(`${baseUrl}/`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+ });
+
+ // Coinflip players in another group
+
+ group('Coinflip game', function () {
+ // save response as variable
+ let res = http.get(`${baseUrl}/flip_coin.php?bet=heads`);
+ // add duration property to metric
+ coinflipLatency.add(res.timings.duration);
+ sleep(1);
+
+ res = http.get(`${baseUrl}/flip_coin.php?bet=tails`);
+ // add duration property to metric
+ coinflipLatency.add(res.timings.duration);
+ sleep(1);
+ });
+}
+```
+
+{{< /code >}}
+
+## Modularize logic
+
+With [modules](https://grafana.com/docs/k6//using-k6/modules), you can use logic and variables from other files.
+Use modules to extract the functions to their own files.
+
+To do so, follow these steps:
+
+1. Copy the previous script (`whole-tutorial.js`) and save it as `main.js`.
+1. Extract the `Contacts flow` group function from `main.js` script file and paste it into a new file called `contacts.js`
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { group, sleep } from 'k6';
+
+export function contacts() {
+ const baseUrl = 'https://test.k6.io';
+ const contactsLatency = [];
+
+ group('Contacts flow', function () {
+ // save response as variable
+ let res = http.get(`${baseUrl}/contacts.php`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+
+ res = http.get(`${baseUrl}/`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+ });
+}
+```
+
+{{< /code >}}
+
+As is, this script won't work, since it has undeclared functions and variables.
+
+1. Add the necessary imports and variables. This script uses the `group`, `sleep`, and `http` functions or libraries. It also has a custom metric. Since this metric is specific to the group, you can add it `contacts.js`.
+
+1. Finally, pass `baseUrl` as a parameter of the `contacts` function.
+
+ {{< code >}}
+
+ ```javascript
+ import http from 'k6/http';
+ import { Trend } from 'k6/metrics';
+ import { group, sleep } from 'k6';
+
+ const contactsLatency = new Trend('contact_duration');
+
+ export function contacts(baseUrl) {
+ group('Contacts flow', function () {
+ // save response as variable
+ let res = http.get(`${baseUrl}/contacts.php`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+
+ res = http.get(`${baseUrl}/`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+ });
+ }
+ ```
+
+ {{< /code >}}
+
+1. Repeat the process with the `coinflip` group in a file called `coinflip.js`.
+ Use the tabs to see the final three files should (`options` moved to the bottom of `main.js` for better readability).
+
+ {{< code >}}
+
+ ```main
+ import { contacts } from './contacts.js';
+ import { coinflip } from './coinflip.js';
+
+ const baseUrl = 'https://test.k6.io';
+
+ export default function () {
+ // Put visits to contact page in one group
+ contacts(baseUrl);
+ // Coinflip players in another group
+ coinflip(baseUrl);
+ }
+
+ //define configuration
+ export const options = {
+ scenarios: {
+ //arbitrary name of scenario:
+ breaking: {
+ executor: 'ramping-vus',
+ stages: [
+ { duration: '10s', target: 20 },
+ { duration: '50s', target: 20 },
+ { duration: '50s', target: 40 },
+ { duration: '50s', target: 60 },
+ { duration: '50s', target: 80 },
+ { duration: '50s', target: 100 },
+ { duration: '50s', target: 120 },
+ { duration: '50s', target: 140 },
+ //....
+ ],
+ },
+ },
+ //define thresholds
+ thresholds: {
+ http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // availability threshold for error rate
+ http_req_duration: ['p(99)<1000'], // Latency threshold for percentile
+ },
+ };
+ ```
+
+ ```contacts
+ import http from 'k6/http';
+ import { Trend } from 'k6/metrics';
+ import { group, sleep } from 'k6';
+
+ const contactsLatency = new Trend('contact_duration');
+
+ export function contacts(baseUrl) {
+ group('Contacts flow', function () {
+ // save response as variable
+ let res = http.get(`${baseUrl}/contacts.php`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+
+ res = http.get(`${baseUrl}/`);
+ // add duration property to metric
+ contactsLatency.add(res.timings.duration);
+ sleep(1);
+ });
+ }
+ ```
+
+ ```coinflip
+ import http from 'k6/http';
+ import { Trend } from 'k6/metrics';
+ import { group, sleep } from 'k6';
+
+ const coinflipLatency = new Trend('coinflip_duration');
+
+ export function coinflip(baseUrl) {
+ group('Coinflip game', function () {
+ // save response as variable
+ let res = http.get(`${baseUrl}/flip_coin.php?bet=heads`);
+ // add duration property to metric
+ coinflipLatency.add(res.timings.duration);
+ sleep(1);
+ // mutate for new request
+ res = http.get(`${baseUrl}/flip_coin.php?bet=tails`);
+ // add duration property to metric
+ coinflipLatency.add(res.timings.duration);
+ sleep(1);
+ });
+ }
+ ```
+
+ {{< /code >}}
+
+ Run the test:
+
+ ```bash
+ # setting the workload to 10 iterations to limit run time
+ k6 run main.js --iterations 10
+ ```
+
+ The results should be very similar to running the script in a combined file, since these are the same test.
+
+## Modularize workload
+
+Now that the iteration code is totally modularized, you might modularize your `options`, too.
+
+The following example creates a module `config.js` to export the threshold and workload settings.
+
+{{< code >}}
+
+```main
+import { coinflip } from './coinflip.js';
+import { contacts } from './contacts.js';
+import { thresholdsSettings, breakingWorkload } from './config.js';
+
+export const options = {
+ scenarios: { breaking: breakingWorkload },
+ thresholds: thresholdsSettings,
+};
+
+const baseUrl = 'https://test.k6.io';
+
+export default function () {
+ contacts(baseUrl);
+ coinflip(baseUrl);
+}
+```
+
+```config
+export const thresholdsSettings = {
+ http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }],
+ http_req_duration: ['p(99)<1000'],
+};
+
+export const breakingWorkload = {
+ executor: 'ramping-vus',
+ stages: [
+ { duration: '10s', target: 20 },
+ { duration: '50s', target: 20 },
+ { duration: '50s', target: 40 },
+ { duration: '50s', target: 60 },
+ { duration: '50s', target: 80 },
+ { duration: '50s', target: 100 },
+ { duration: '50s', target: 120 },
+ { duration: '50s', target: 140 },
+ //....
+ ],
+};
+```
+
+{{< /code >}}
+
+Notice the length of this final script and compare it with the script at the beginning of this page.
+Though the final execution is the same, it's half the size and more readable.
+
+Besides shortness, this modularity lets you compose scripts from many parts, or dynamically configure scripts at run time.
+
+## Mix and match logic
+
+With modularized configuration and logic, you can mix and match logic.
+An easy way to configure this is through [environment variables](https://grafana.com/docs/k6//using-k6/environment-variables).
+
+Change `main.js` and `config.js` so that it:
+
+- _By default_ runs a smoke test with 5 iterations
+- With the right environment variable value, runs a breaking test
+
+To do this, follow these steps:
+
+1. Add the workload settings for configuring the smoke test to `config.js`:
+
+ {{< code >}}
+
+ ```javascript
+ export const smokeWorkload = {
+ executor: 'shared-iterations',
+ iterations: 5,
+ vus: 1,
+ };
+
+ export const thresholdsSettings = {
+ http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }],
+ http_req_duration: ['p(99)<1000'],
+ };
+
+ export const breakingWorkload = {
+ executor: 'ramping-vus',
+ stages: [
+ { duration: '10s', target: 20 },
+ { duration: '50s', target: 20 },
+ { duration: '50s', target: 40 },
+ { duration: '50s', target: 60 },
+ { duration: '50s', target: 80 },
+ { duration: '50s', target: 100 },
+ { duration: '50s', target: 120 },
+ { duration: '50s', target: 140 },
+ //....
+ ],
+ };
+ ```
+
+ {{< /code >}}
+
+1. Edit `main.js` to choose the workload settings depending on the `WORKLOAD` environment variable. For example:
+
+ {{< code >}}
+
+ ```javascript
+ import { coinflip } from './coinflip.js';
+ import { contacts } from './contacts.js';
+ import { thresholdsSettings, breakingWorkload, smokeWorkload } from './config.js';
+
+ export const options = {
+ scenarios: {
+ my_scenario: __ENV.WORKLOAD === 'breaking' ? breakingWorkload : smokeWorkload,
+ },
+ thresholds: thresholdsSettings,
+ };
+
+ const baseUrl = 'https://test.k6.io';
+
+ export default function () {
+ contacts(baseUrl);
+ coinflip(baseUrl);
+ }
+ ```
+
+ {{< /code >}}
+
+1. Run the script with and without the `-e` flag.
+
+ - What happens when you run `k6 run main.js`?
+ - What about `k6 run main.js -e WORKLOAD=breaking`?
+
+This was a simple example to showcase how you can modularize a test.
+As your test suite grows and more people are involved in performance testing, your modularization strategy becomes essential to building and maintaining an efficient testing suite.
+
+## Next steps
+
+Now you've seen examples to write tests, assert for performance, filter results, and modularize scripts.
+Notice how the tests progress in complexity: from single endpoints to holistic tests, from small to large loads, and from single tests to reusable modules. These progressions are typical in testing, with the next step being to automate. It might be impractical to automate a tutorial, but if you are interested,
+read the [Automated performance testing](https://grafana.com/docs/k6//testing-guides/automated-performance-testing) guide.
+
+More likely, you want to learn more about k6. The [k6-learn repository](https://github.com/grafana/k6-learn) has more details to practice.
+Or, you can read and explore the [testing guides](https://grafana.com/docs/k6//testing-guides) and try to build out your testing strategy.
+
+Happy testing!
diff --git a/docs/sources/v0.50.x/examples/get-started-with-k6/test-for-functional-behavior.md b/docs/sources/v0.50.x/examples/get-started-with-k6/test-for-functional-behavior.md
new file mode 100644
index 000000000..1968592a5
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/get-started-with-k6/test-for-functional-behavior.md
@@ -0,0 +1,161 @@
+---
+title: Test for functional behavior
+description: Use k6 to write requests and assert that they respond correctly
+weight: 100
+---
+
+# Test for functional behavior
+
+In this tutorial, learn how to write a test that does the following:
+
+- Sends a POST request to the new endpoint
+- Creates a check for the response status
+
+## Script the Request
+
+The first thing to do is to add logic for the endpoint.
+To do that, you need to make an [HTTP request](https://grafana.com/docs/k6//using-k6/http-requests):
+
+1. Import the HTTP module.
+2. Create a payload to authenticate the user.
+3. Use the [`http.post`](https://grafana.com/docs/k6//javascript-api/k6-http/post) method to send your request with the payload to an endpoint.
+
+To test, copy this file and save it as `api-test.js`.
+
+{{< code >}}
+
+```javascript
+// import necessary module
+import http from 'k6/http';
+
+export default function () {
+ // define URL and payload
+ const url = 'https://test-api.k6.io/auth/basic/login/';
+ const payload = JSON.stringify({
+ username: 'test_case',
+ password: '1234',
+ });
+
+ const params = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ };
+
+ // send a post request and save response as a variable
+ const res = http.post(url, payload, params);
+}
+```
+
+{{< /code >}}
+
+Run the script using the `k6 run` command:
+
+```bash
+k6 run api-test.js
+```
+
+After the test finishes, k6 reports the [default result summary](https://grafana.com/docs/k6//results-output/end-of-test#the-default-summary).
+
+```bash
+ /\ |‾‾| /‾‾/ /‾‾/
+ /\ / \ | |/ / / /
+ / \/ \ | ( / ‾‾\
+ / \ | |\ \ | (‾) |
+ / __________ \ |__| \__\ \_____/ .io
+
+ execution: local
+ script: api-test.js
+ output: -
+ ...
+```
+
+As an optional step, you can log the response body to the console to make sure you're getting the right response.
+
+{{< code >}}
+
+```javascript
+import { http } from 'k6/http';
+
+export default function () {
+ // ...
+
+ const res = http.post('https://test-api.k6.io', JSON.stringify({ foo: 'bar' }), {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ // Log the request body
+ console.log(res.body);
+}
+```
+
+{{< /code >}}
+
+## Add response checks
+
+Once you're sure the request is well-formed, add a [check](https://grafana.com/docs/k6//using-k6/checks) that validates whether the system responds with the expected status code.
+
+1. Update your script so it has the following check function.
+
+{{< code >}}
+
+```javascript
+// Import necessary modules
+import { check } from 'k6';
+import http from 'k6/http';
+
+export default function () {
+ // define URL and request body
+ const url = 'https://test-api.k6.io/auth/basic/login/';
+ const payload = JSON.stringify({
+ username: 'test_case',
+ password: '1234',
+ });
+ const params = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ };
+
+ // send a post request and save response as a variable
+ const res = http.post(url, payload, params);
+
+ // check that response is 200
+ check(res, {
+ 'response code was 200': (res) => res.status == 200,
+ });
+}
+```
+
+{{< /code >}}
+
+1. Run the script again.
+
+```bash
+k6 run api-test.js
+```
+
+1. Inspect the result output for your check.
+ It should look something like this.
+
+ ```
+ ✓ response code was 200
+ ```
+
+{{% admonition type="note" %}}
+
+Under larger loads, this check will fail in some iterations.
+**Failed checks do not stop tests.**
+
+Rather, k6 tracks the success rate and presents it in your [end of test](https://grafana.com/docs/k6//results-output/end-of-test) summary.
+
+{{% /admonition %}}
+
+## Next steps
+
+In this tutorial, you've used k6 to make a POST request and check that it responds with a `200` status.
+
+However, these tests make only one request, which doesn't say much about how the system will respond under load.
+For that, you need to [test under load](https://grafana.com/docs/k6//examples/get-started-with-k6/test-for-performance).
diff --git a/docs/sources/v0.50.x/examples/get-started-with-k6/test-for-performance.md b/docs/sources/v0.50.x/examples/get-started-with-k6/test-for-performance.md
new file mode 100644
index 000000000..c4519fea1
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/get-started-with-k6/test-for-performance.md
@@ -0,0 +1,299 @@
+---
+title: Test for performance
+description: Write thresholds to evaluate performance criteria, then increase load to see how the system performs.
+weight: 200
+---
+
+# Test for performance
+
+In the previous section, you made a working script to test an endpoint functionality.
+The next step is to test how this system responds under load.
+This requires setting up a few [`options`](https://grafana.com/docs/k6//using-k6/k6-options) to configure the parts of the test that don't deal with test logic.
+
+In this tutorial, learn how to:
+
+- Use [thresholds](https://grafana.com/docs/k6//using-k6/thresholds) to assert for performance criteria
+- Configure load increases through [scenarios](https://grafana.com/docs/k6//using-k6/scenarios)
+
+These examples build on the script from the [previous section](https://grafana.com/docs/k6//examples/get-started-with-k6/test-for-functional-behavior).
+
+## Context: meet service-level objectives
+
+To assess the login endpoint's performance, your team may have defined [service level objectives](https://sre.google/sre-book/service-level-objectives/) (SLOs). For example:
+
+- 99% of requests should be successful
+- 99% of requests should have a latency of 1000ms or less
+
+The service must meet these SLOs under different types of usual traffic.
+
+## Assert for performance with thresholds
+
+To codify the SLOs, add [_thresholds_](https://grafana.com/docs/k6//using-k6/thresholds) to test that your system performs to its goal criteria.
+
+Thresholds are set in the [`options`](https://grafana.com/docs/k6//using-k6/k6-options) object.
+
+```javascript
+export const options = {
+ // define thresholds
+ thresholds: {
+ http_req_failed: ['rate<0.01'], // http errors should be less than 1%
+ http_req_duration: ['p(99)<1000'], // 99% of requests should be below 1s
+ },
+};
+```
+
+Add this `options` object with thresholds to your script `api-test.js`.
+
+{{< code >}}
+
+```javascript
+// import necessary modules
+import { check } from 'k6';
+import http from 'k6/http';
+
+// define configuration
+export const options = {
+ // define thresholds
+ thresholds: {
+ http_req_failed: ['rate<0.01'], // http errors should be less than 1%
+ http_req_duration: ['p(99)<1000'], // 99% of requests should be below 1s
+ },
+};
+
+export default function () {
+ // define URL and request body
+ const url = 'https://test-api.k6.io/auth/basic/login/';
+ const payload = JSON.stringify({
+ username: 'test_case',
+ password: '1234',
+ });
+ const params = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ };
+
+ // send a post request and save response as a variable
+ const res = http.post(url, payload, params);
+
+ // check that response is 200
+ check(res, {
+ 'response code was 200': (res) => res.status == 200,
+ });
+}
+```
+
+{{< /code >}}
+
+Run the test.
+
+```bash
+k6 run api-test.js
+```
+
+Inspect the console output to determine whether performance crossed a threshold.
+
+```
+✓ http_req_duration..............: avg=66.14ms min=0s med=0s max=198.42ms p(90)=158.73ms p(95)=178.58ms
+ { expected_response:true }...: avg=198.42ms min=198.42ms med=198.42ms max=198.42ms p(90)=198.42ms p(95)=198.42ms
+✗ http_req_failed................: 0.00% ✓ 0 ✗ 1
+```
+
+The ✓ and ✗ symbols indicate whether the performance thresholds passed or failed.
+
+## Test performance under increasing load
+
+Now your script has logic to simulate user behavior, and assertions for functionality (checks) and performance (thresholds).
+
+It's time to increase the load to see how it performs.
+To increase the load, use the scenarios property.
+Scenarios schedule load according to the number of VUs, number of iterations, VUs, or by iteration rate.
+
+### Run a smoke test
+
+Start small. Run a [smoke test](https://grafana.com/docs/k6//testing-guides/test-types/smoke-testing 'a small test to confirm the script works properly') to check that your script can handle a minimal load.
+
+To do so, use the [`--iterations`](https://grafana.com/docs/k6//using-k6/k6-options/reference#iterations) flag with an argument of 10 or fewer.
+
+```bash
+k6 run --iterations 10 api-test.js
+```
+
+If the service can't receive 10 iterations, the system has some serious performance issues to debug.
+Good thing you ran the test early!
+
+### Run a test against an average load
+
+Generally, traffic doesn't arrive all at once.
+Rather, it gradually increases to a peak load.
+To simulate this, testers increase the load in _stages_.
+
+Add the following `scenario` property to your `options` object and rerun the test.
+
+```javascript
+export const options = {
+ // define thresholds
+ thresholds: {
+ http_req_failed: ['rate<0.01'], // http errors should be less than 1%
+ http_req_duration: ['p(99)<1000'], // 99% of requests should be below 1s
+ },
+ // define scenarios
+ scenarios: {
+ // arbitrary name of scenario
+ average_load: {
+ executor: 'ramping-vus',
+ stages: [
+ // ramp up to average load of 20 virtual users
+ { duration: '10s', target: 20 },
+ // maintain load
+ { duration: '50s', target: 20 },
+ // ramp down to zero
+ { duration: '5s', target: 0 },
+ ],
+ },
+ },
+};
+```
+
+Since this is a learning environment, the stages are still quite short.
+Where the smoke test defined the load in terms of iterations, this configuration uses the [`ramping-vus` executor](https://grafana.com/docs/k6//using-k6/scenarios/executors/ramping-vus) to express load through virtual users and duration.
+
+Run the test with no command-line flags:
+
+```bash
+k6 run api-test.js
+```
+
+The load is small, so the server should perform within thresholds.
+However, this test server may be under load by many k6 learners, so the results are unpredictable.
+
+{{% admonition type="note" %}}
+
+At this point, it'd be nice to have a graphical interface to visualize metrics as they occur.
+k6 has many output formats, which can serve as inputs for many visualization tools, both open source and commercial.
+For ideas, read [Ways to visualize k6 results](https://k6.io/blog/ways-to-visualize-k6-results).
+
+{{% /admonition %}}
+
+### Ramp up until threshold fails
+
+Finally, run a [breakpoint test](https://grafana.com/docs/k6//testing-guides/test-types/breakpoint-testing), where you probe the system's limits.
+In this case, run the test until the availability (error rate) threshold is crossed.
+
+To do this:
+
+1. Add the `abortOnFail` property to `http_req_failed`.
+
+```javascript
+const options = {
+ thresholds: {
+ http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // http errors should be less than 1%, otherwise abort the test
+ },
+};
+```
+
+1. Update the `scenarios` property to ramp the test up until it fails.
+
+```javascript
+export const options = {
+ thresholds: {
+ http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }],
+ http_req_duration: ['p(99)<1000'],
+ },
+ scenarios: {
+ // define scenarios
+ breaking: {
+ executor: 'ramping-vus',
+ stages: [
+ { duration: '10s', target: 20 },
+ { duration: '50s', target: 20 },
+ { duration: '50s', target: 40 },
+ { duration: '50s', target: 60 },
+ { duration: '50s', target: 80 },
+ { duration: '50s', target: 100 },
+ { duration: '50s', target: 120 },
+ { duration: '50s', target: 140 },
+ //....
+ ],
+ },
+ },
+};
+```
+
+Here is the full script.
+
+{{< code >}}
+
+```javascript
+// import necessary modules
+import { check } from 'k6';
+import http from 'k6/http';
+
+// define configuration
+export const options = {
+ // define thresholds
+ thresholds: {
+ http_req_failed: [{ threshold: 'rate<0.01', abortOnFail: true }], // availability threshold for error rate
+ http_req_duration: ['p(99)<1000'], // Latency threshold for percentile
+ },
+ // define scenarios
+ scenarios: {
+ breaking: {
+ executor: 'ramping-vus',
+ stages: [
+ { duration: '10s', target: 20 },
+ { duration: '50s', target: 20 },
+ { duration: '50s', target: 40 },
+ { duration: '50s', target: 60 },
+ { duration: '50s', target: 80 },
+ { duration: '50s', target: 100 },
+ { duration: '50s', target: 120 },
+ { duration: '50s', target: 140 },
+ //....
+ ],
+ },
+ },
+};
+
+export default function () {
+ // define URL and request body
+ const url = 'https://test-api.k6.io/auth/basic/login/';
+ const payload = JSON.stringify({
+ username: 'test_case',
+ password: '1234',
+ });
+ const params = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ };
+
+ // send a post request and save response as a variable
+ const res = http.post(url, payload, params);
+
+ // check that response is 200
+ check(res, {
+ 'response code was 200': (res) => res.status == 200,
+ });
+}
+```
+
+{{< /code >}}
+
+Run the test.
+
+```bash
+k6 run api-test.js
+```
+
+Did the threshold fail? If not, add another stage with a higher target and try again. Repeat until the threshold aborts the test:
+
+```bash
+ERRO[0010] thresholds on metrics 'http_req_duration, http_req_failed' were breached; at least one has abortOnFail enabled, stopping test prematurely
+```
+
+## Next steps
+
+In this tutorial, you used [thresholds](https://grafana.com/docs/k6//using-k6/thresholds) to assert performance and [Scenarios](https://grafana.com/docs/k6//using-k6/scenarios) to schedule different load patterns. To learn more about the usual load patterns and their goals, read [Load Test Types](https://grafana.com/docs/k6//testing-guides/test-types/)
+
+The [next step of this tutorial shows how to interpret test results](https://grafana.com/docs/k6//examples/get-started-with-k6/analyze-results). This involves filtering results and adding custom metrics.
diff --git a/docs/sources/v0.50.x/examples/get-timings-for-an-http-metric.md b/docs/sources/v0.50.x/examples/get-timings-for-an-http-metric.md
new file mode 100644
index 000000000..03a464583
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/get-timings-for-an-http-metric.md
@@ -0,0 +1,47 @@
+---
+title: Get timings for an HTTP metric
+description: How to calculate timings for an individual k6 metric
+weight: 23
+---
+
+# Get timings for an HTTP metric
+
+To access the timing information from an individual HTTP request, the [Response.timings](https://grafana.com/docs/k6//javascript-api/k6-http/response) object provides the time spent on the various phases in `ms`.
+One use case of this is to use the timings in a [Custom metric](https://grafana.com/docs/k6//using-k6/metrics/create-custom-metrics) to make a trend for a specific endpoint.
+
+The timings are as follows:
+
+- blocked: equals to `http_req_blocked`.
+- connecting: equals to `http_req_connecting`.
+- tls_handshaking: equals to `http_req_tls_handshaking`.
+- sending: equals to `http_req_sending`.
+- waiting: equals to `http_req_waiting`.
+- receiving: equals to `http_req_receiving`.
+- duration: equals to `http_req_duration`.
+
+This script gets the request duration timing for a specific GET request and logs it to the console.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+
+export default function () {
+ const res = http.get('https://httpbin.test.k6.io');
+ console.log('Response time was ' + String(res.timings.duration) + ' ms');
+}
+```
+
+{{< /code >}}
+
+The expected (partial) output looks like this:
+
+{{< code >}}
+
+```bash
+$ k6 run script.js
+
+ INFO[0001] Response time was 337.962473 ms source=console
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/html-forms.md b/docs/sources/v0.50.x/examples/html-forms.md
new file mode 100644
index 000000000..e640c3b04
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/html-forms.md
@@ -0,0 +1,39 @@
+---
+title: 'HTML Forms'
+description: 'Scripting example on how to handle HTML forms in a k6 test.'
+weight: 07
+---
+
+# HTML Forms
+
+Scripting example on how to handle HTML forms.
+
+In many cases using the [Selection](https://grafana.com/docs/k6//javascript-api/k6-html/selection) API (jQuery API clone) to interact with HTML data is enough, but for some use cases, like with forms, we can make things easier providing a higher-level API like the [Response.submitForm( [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/response/response-submitform) API.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { sleep } from 'k6';
+
+export default function () {
+ // Request page containing a form
+ let res = http.get('https://httpbin.test.k6.io/forms/post');
+
+ // Now, submit form setting/overriding some fields of the form
+ res = res.submitForm({
+ formSelector: 'form',
+ fields: { custname: 'test', extradata: 'test2' },
+ });
+ sleep(3);
+}
+```
+
+{{< /code >}}
+
+**Relevant k6 APIs**:
+
+- [Response.submitForm([params])](https://grafana.com/docs/k6//javascript-api/k6-http/response/response-submitform)
+- [Selection.find(selector)](https://grafana.com/docs/k6//javascript-api/k6-html/selection/selection-find)
+ (the [jQuery Selector API](http://api.jquery.com/category/selectors/) docs are also a good
+ resource on what possible selector queries can be made)
diff --git a/docs/sources/v0.50.x/examples/http-authentication.md b/docs/sources/v0.50.x/examples/http-authentication.md
new file mode 100644
index 000000000..216f4731f
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/http-authentication.md
@@ -0,0 +1,183 @@
+---
+title: 'HTTP Authentication'
+description: 'Scripting examples on how to use different authentication or authorization methods in your load test.'
+weight: 02
+---
+
+# HTTP Authentication
+
+Scripting examples on how to use different authentication or authorization methods in your load test.
+
+## Basic authentication
+
+{{< code >}}
+
+```javascript
+import encoding from 'k6/encoding';
+import http from 'k6/http';
+import { check } from 'k6';
+
+const username = 'user';
+const password = 'passwd';
+
+export default function () {
+ const credentials = `${username}:${password}`;
+
+ // Passing username and password as part of the URL will
+ // allow us to authenticate using HTTP Basic Auth.
+ const url = `https://${credentials}@httpbin.test.k6.io/basic-auth/${username}/${password}`;
+
+ let res = http.get(url);
+
+ // Verify response
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ 'is authenticated': (r) => r.json().authenticated === true,
+ 'is correct user': (r) => r.json().user === username,
+ });
+
+ // Alternatively you can create the header yourself to authenticate
+ // using HTTP Basic Auth
+ const encodedCredentials = encoding.b64encode(credentials);
+ const options = {
+ headers: {
+ Authorization: `Basic ${encodedCredentials}`,
+ },
+ };
+
+ res = http.get(`https://httpbin.test.k6.io/basic-auth/${username}/${password}`, options);
+
+ // Verify response (checking the echoed data from the httpbin.test.k6.io
+ // basic auth test API endpoint)
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ 'is authenticated': (r) => r.json().authenticated === true,
+ 'is correct user': (r) => r.json().user === username,
+ });
+}
+```
+
+{{< /code >}}
+
+## Digest authentication
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+const username = 'user';
+const password = 'passwd';
+
+export default function () {
+ // Passing username and password as part of URL plus the auth option will
+ // authenticate using HTTP Digest authentication.
+ const credentials = `${username}:${password}`;
+ const res = http.get(
+ `https://${credentials}@httpbin.test.k6.io/digest-auth/auth/${username}/${password}`,
+ {
+ auth: 'digest',
+ }
+ );
+
+ // Verify response (checking the echoed data from the httpbin.test.k6.io digest auth
+ // test API endpoint)
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ 'is authenticated': (r) => r.json().authenticated === true,
+ 'is correct user': (r) => r.json().user === username,
+ });
+}
+```
+
+{{< /code >}}
+
+## NTLM authentication
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+
+const username = 'user';
+const password = 'passwd';
+
+export default function () {
+ // Passing username and password as part of URL and then specifying
+ // "ntlm" as auth type will do the trick!
+ const credentials = `${username}:${password}`;
+ const res = http.get(`http://${credentials}@example.com/`, { auth: 'ntlm' });
+}
+```
+
+{{< /code >}}
+
+## AWS Signature v4 authentication with the [k6-jslib-aws](https://github.com/grafana/k6-jslib-aws)
+
+To authenticate requests to AWS APIs using [AWS Signature Version 4](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html), k6 offers the [k6-jslib-aws](https://github.com/grafana/k6-jslib-aws) JavaScript library, which provides a dedicated `SignatureV4` class. This class can produce authenticated requests to send to AWS APIs using the `http` k6 module.
+
+Here's an example script to demonstrate how to sign a request to fetch an object from an S3 bucket:
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { AWSConfig, SignatureV4 } from 'https://jslib.k6.io/aws/0.11.0/signature.js';
+
+const awsConfig = new AWSConfig({
+ region: __ENV.AWS_REGION,
+ accessKeyId: __ENV.AWS_ACCESS_KEY_ID,
+ secretAccessKey: __ENV.AWS_SECRET_ACCESS_KEY,
+
+ /**
+ * Optional session token for temporary credentials.
+ */
+ sessionToken: __ENV.AWS_SESSION_TOKEN,
+});
+
+export default function () {
+ /**
+ * Create a signer instance with the AWS credentials.
+ * The signer will be used to sign the request.
+ */
+ const signer = new SignatureV4({
+ service: 's3',
+ region: awsConfig.region,
+ credentials: {
+ accessKeyId: awsConfig.accessKeyId,
+ secretAccessKey: awsConfig.secretAccessKey,
+ sessionToken: awsConfig.sessionToken,
+ },
+ });
+
+ /**
+ * Use the signer to prepare a signed request.
+ * The signed request can then be used to send the request to the AWS API.
+ */
+ const signedRequest = signer.sign(
+ {
+ method: 'GET',
+ protocol: 'https',
+ hostname: 'test-jslib-aws.s3.us-east-1.amazonaws.com',
+ path: '/bonjour.txt',
+ headers: {},
+ uriEscapePath: false,
+ applyChecksum: false,
+ },
+ {
+ signingDate: new Date(),
+ signingService: 's3',
+ signingRegion: 'us-east-1',
+ }
+ );
+
+ /**
+ * The `signedRequest` object contains the signed request URL and headers.
+ * We can use them to send the request to the AWS API.
+ */
+ http.get(signedRequest.url, { headers: signedRequest.headers });
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/http2.md b/docs/sources/v0.50.x/examples/http2.md
new file mode 100644
index 000000000..cf45edb81
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/http2.md
@@ -0,0 +1,28 @@
+---
+title: 'HTTP2'
+description: 'Information on how to load test HTTP/2.'
+weight: 12
+---
+
+# HTTP2
+
+If the target system indicates that a connection can be upgraded from HTTP/1.1 to HTTP/2, k6 will do so automatically.
+
+## Making HTTP/2 requests
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+export default function () {
+ const res = http.get('https://test-api.k6.io/');
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ 'protocol is HTTP/2': (r) => r.proto === 'HTTP/2.0',
+ });
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/instant-load-increase.md b/docs/sources/v0.50.x/examples/instant-load-increase.md
new file mode 100644
index 000000000..0e5fbabe8
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/instant-load-increase.md
@@ -0,0 +1,59 @@
+---
+title: 'Instant load increase'
+description: 'Scripting example on how to instantly increase the number of VUs or iterations and hold them for a period of time'
+draft: 'false'
+weight: 22
+---
+
+# Instant load increase
+
+One of the common usages of load testing tools it's the so-called stepped arrival rate.
+
+In k6 we can achieve it with the following configuration.
+
+Here's an example on how to instantly increase the number of iterations and hold them for a period of time.
+
+{{< code >}}
+
+```javascript
+export const options = {
+ scenarios: {
+ contacts: {
+ executor: 'ramping-arrival-rate',
+ preAllocatedVUs: 50,
+ timeUnit: '1s',
+ startRate: 50,
+ stages: [
+ { target: 200, duration: '30s' }, // linearly go from 50 iters/s to 200 iters/s for 30s
+ { target: 500, duration: '0' }, // instantly jump to 500 iters/s
+ { target: 500, duration: '10m' }, // continue with 500 iters/s for 10 minutes
+ ],
+ },
+ },
+};
+```
+
+{{< /code >}}
+
+Here's an example on how to instantly increase the number of VUs and hold them for a period of time.
+
+{{< code >}}
+
+```javascript
+export const options = {
+ scenarios: {
+ contacts: {
+ executor: 'ramping-vus',
+ preAllocatedVUs: 10,
+ startVUs: 3,
+ stages: [
+ { target: 20, duration: '30s' }, // linearly go from 3 VUs to 20 VUs for 30s
+ { target: 100, duration: '0' }, // instantly jump to 100 VUs
+ { target: 100, duration: '10m' }, // continue with 100 VUs for 10 minutes
+ ],
+ },
+ },
+};
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/oauth-authentication.md b/docs/sources/v0.50.x/examples/oauth-authentication.md
new file mode 100644
index 000000000..adcd58d46
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/oauth-authentication.md
@@ -0,0 +1,332 @@
+---
+title: 'OAuth Authentication'
+description: 'Scripting examples on how to use OAuth authentication in your load test.'
+weight: 03
+---
+
+# OAuth Authentication
+
+Scripting examples on how to use OAuth authentication in your load test.
+
+## OAuth authentication
+
+The following examples take a set of arguments, shown in the function documentation, and returns the response body as JSON so that you can extract the token from.
+
+### Azure Active Directory
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+
+/**
+ * Authenticate using OAuth against Azure Active Directory
+ * @function
+ * @param {string} tenantId - Directory ID in Azure
+ * @param {string} clientId - Application ID in Azure
+ * @param {string} clientSecret - Can be obtained from https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad-app#create-a-client-secret
+ * @param {string} scope - Space-separated list of scopes (permissions) that are already given consent to by admin
+ * @param {string} resource - Either a resource ID (as string) or an object containing username and password
+ */
+export function authenticateUsingAzure(tenantId, clientId, clientSecret, scope, resource) {
+ let url;
+ const requestBody = {
+ client_id: clientId,
+ client_secret: clientSecret,
+ scope: scope,
+ };
+
+ if (typeof resource == 'string') {
+ url = `https://login.microsoftonline.com/${tenantId}/oauth2/token`;
+ requestBody['grant_type'] = 'client_credentials';
+ requestBody['resource'] = resource;
+ } else if (
+ typeof resource == 'object' &&
+ resource.hasOwnProperty('username') &&
+ resource.hasOwnProperty('password')
+ ) {
+ url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
+ requestBody['grant_type'] = 'password';
+ requestBody['username'] = resource.username;
+ requestBody['password'] = resource.password;
+ } else {
+ throw 'resource should be either a string or an object containing username and password';
+ }
+
+ const response = http.post(url, requestBody);
+
+ return response.json();
+}
+```
+
+{{< /code >}}
+
+### Azure B2C
+
+The following example shows how you can authenticate with Azure B2C using the [Client Credentials Flow](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-oauth-code#client-credentials-flow).
+
+This example is based on a JMeter example found at the [azure-ad-b2c/load-tests](https://github.com/azure-ad-b2c/load-tests) repository.
+
+To use this script, you need to:
+
+1. [Set up your own Azure B2C tenant](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant)
+ - Copy the tenant name, it will be used in your test script.
+1. [Register a web application](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga)
+ - Register a single page application with the redirect URL of: https://jwt.ms. That's needed for the flow to receive a token.
+ - After the creation, you can get the Application (client) ID, and the Directory (tenant) ID. Copy both of them, they'll be used in your test script.
+1. [Create a user flow so that you can sign up and create a user](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows)
+ - Create a new user, and copy the username and password. They'll be used in the test script.
+
+You can find the settings in the B2C settings in the Azure portal if you need to refer to them later on. Make sure to fill out all the variables for the `B2CGraphSettings` object, as well as replace `USERNAME` and `PASSWORD` in `export default function`.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import crypto from 'k6/crypto';
+import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
+
+const B2CGraphSettings = {
+ B2C: {
+ client_id: '', // Application ID in Azure
+ user_flow_name: '',
+ tenant_id: '', // Directory ID in Azure
+ tenant_name: '',
+ scope: 'openid',
+ redirect_url: 'https://jwt.ms',
+ },
+};
+
+/**
+ * Authenticate using OAuth against Azure B2C
+ * @function
+ * @param {string} username - Username of the user to authenticate
+ * @param {string} password
+ * @return {string} id_token
+ */
+export function GetB2cIdToken(username, password) {
+ const state = GetState();
+ SelfAsserted(state, username, password);
+ const code = CombinedSigninAndSignup(state);
+ return GetToken(code, state.codeVerifier);
+}
+
+/**
+ * @typedef {object} b2cStateProperties
+ * @property {string} csrfToken
+ * @property {string} stateProperty
+ * @property {string} codeVerifier
+ *
+ */
+
+/**
+ * Get the id token from Azure B2C
+ * @function
+ * @param {string} code
+ * @returns {string} id_token
+ */
+const GetToken = (code, codeVerifier) => {
+ const url =
+ `https://${B2CGraphSettings.B2C.tenant_name}.b2clogin.com/${B2CGraphSettings.B2C.tenant_id}` +
+ `/oauth2/v2.0/token` +
+ `?p=${B2CGraphSettings.B2C.user_flow_name}` +
+ `&client_id=${B2CGraphSettings.B2C.client_id}` +
+ `&grant_type=authorization_code` +
+ `&scope=${B2CGraphSettings.B2C.scope}` +
+ `&code=${code}` +
+ `&redirect_uri=${B2CGraphSettings.B2C.redirect_url}` +
+ `&code_verifier=${codeVerifier}`;
+
+ const response = http.post(url, '', {
+ tags: {
+ b2c_login: 'GetToken',
+ },
+ });
+
+ return JSON.parse(response.body).id_token;
+};
+
+/**
+ * Signs in the user using the CombinedSigninAndSignup policy
+ * extraqct B2C code from response
+ * @function
+ * @param {b2cStateProperties} state
+ * @returns {string} code
+ */
+const CombinedSigninAndSignup = (state) => {
+ const url =
+ `https://${B2CGraphSettings.B2C.tenant_name}.b2clogin.com/${B2CGraphSettings.B2C.tenant_name}.onmicrosoft.com` +
+ `/${B2CGraphSettings.B2C.user_flow_name}/api/CombinedSigninAndSignup/confirmed` +
+ `?csrf_token=${state.csrfToken}` +
+ `&rememberMe=false` +
+ `&tx=StateProperties=${state.stateProperty}` +
+ `&p=${B2CGraphSettings.B2C.user_flow_name}`;
+
+ const response = http.get(url, '', {
+ tags: {
+ b2c_login: 'CombinedSigninAndSignup',
+ },
+ });
+ const codeRegex = '.*code=([^"]*)';
+ return response.url.match(codeRegex)[1];
+};
+
+/**
+ * Signs in the user using the SelfAsserted policy
+ * @function
+ * @param {b2cStateProperties} state
+ * @param {string} username
+ * @param {string} password
+ */
+const SelfAsserted = (state, username, password) => {
+ const url =
+ `https://${B2CGraphSettings.B2C.tenant_name}.b2clogin.com/${B2CGraphSettings.B2C.tenant_id}` +
+ `/${B2CGraphSettings.B2C.user_flow_name}/SelfAsserted` +
+ `?tx=StateProperties=${state.stateProperty}` +
+ `&p=${B2CGraphSettings.B2C.user_flow_name}` +
+ `&request_type=RESPONSE` +
+ `&email=${username}` +
+ `&password=${password}`;
+
+ const params = {
+ headers: {
+ 'X-CSRF-TOKEN': `${state.csrfToken}`,
+ },
+ tags: {
+ b2c_login: 'SelfAsserted',
+ },
+ };
+ http.post(url, '', params);
+};
+
+/**
+ * Calls the B2C login page to get the state property
+ * @function
+ * @returns {b2cStateProperties} b2cState
+ */
+const GetState = () => {
+ const nonce = randomString(50);
+ const challenge = crypto.sha256(nonce.toString(), 'base64rawurl');
+
+ const url =
+ `https://${B2CGraphSettings.B2C.tenant_name}.b2clogin.com` +
+ `/${B2CGraphSettings.B2C.tenant_id}/oauth2/v2.0/authorize?` +
+ `p=${B2CGraphSettings.B2C.user_flow_name}` +
+ `&client_id=${B2CGraphSettings.B2C.client_id}` +
+ `&nonce=${nonce}` +
+ `&redirect_uri=${B2CGraphSettings.B2C.redirect_url}` +
+ `&scope=${B2CGraphSettings.B2C.scope}` +
+ `&response_type=code` +
+ `&prompt=login` +
+ `&code_challenge_method=S256` +
+ `&code_challenge=${challenge}` +
+ `&response_mode=fragment`;
+
+ const response = http.get(url, '', {
+ tags: {
+ b2c_login: 'GetCookyAndState',
+ },
+ });
+
+ const vuJar = http.cookieJar();
+ const responseCookies = vuJar.cookiesForURL(response.url);
+
+ const b2cState = {};
+ b2cState.codeVerifier = nonce;
+ b2cState.csrfToken = responseCookies['x-ms-cpim-csrf'][0];
+ b2cState.stateProperty = response.body.match('.*StateProperties=([^"]*)')[1];
+ return b2cState;
+};
+
+/**
+ * Helper function to get the authorization header for a user
+ * @param {user} user
+ * @returns {object} httpsOptions
+ */
+export const GetAuthorizationHeaderForUser = (user) => {
+ const token = GetB2cIdToken(user.username, user.password);
+
+ return {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + token,
+ },
+ };
+};
+
+export default function () {
+ const token = GetB2cIdToken('USERNAME', 'PASSWORD');
+ console.log(token);
+}
+```
+
+{{< /code >}}
+
+### Okta
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import encoding from 'k6/encoding';
+
+/**
+ * Authenticate using OAuth against Okta
+ * @function
+ * @param {string} oktaDomain - Okta domain to authenticate against (e.g. 'k6.okta.com')
+ * @param {string} authServerId - Authentication server identifier (default is 'default')
+ * @param {string} clientId - Generated by Okta automatically
+ * @param {string} clientSecret - Generated by Okta automatically
+ * @param {string} scope - Space-separated list of scopes
+ * @param {string|object} resource - Either a resource ID (as string) or an object containing username and password
+ */
+export function authenticateUsingOkta(
+ oktaDomain,
+ authServerId,
+ clientId,
+ clientSecret,
+ scope,
+ resource
+) {
+ if (authServerId === 'undefined' || authServerId == '') {
+ authServerId = 'default';
+ }
+ const url = `https://${oktaDomain}/oauth2/${authServerId}/v1/token`;
+ const requestBody = { scope: scope };
+ let response;
+
+ if (typeof resource == 'string') {
+ requestBody['grant_type'] = 'client_credentials';
+
+ const encodedCredentials = encoding.b64encode(`${clientId}:${clientSecret}`);
+ const params = {
+ auth: 'basic',
+ headers: {
+ Authorization: `Basic ${encodedCredentials}`,
+ },
+ };
+
+ response = http.post(url, requestBody, params);
+ } else if (
+ typeof resource == 'object' &&
+ resource.hasOwnProperty('username') &&
+ resource.hasOwnProperty('password')
+ ) {
+ requestBody['grant_type'] = 'password';
+ requestBody['username'] = resource.username;
+ requestBody['password'] = resource.password;
+ requestBody['client_id'] = clientId;
+ requestBody['client_secret'] = clientSecret;
+
+ response = http.post(url, requestBody);
+ } else {
+ throw 'resource should be either a string or an object containing username and password';
+ }
+
+ return response.json();
+}
+```
+
+{{< /code >}}
+
+For a detailed example, please visit this article: [How to Load Test OAuth secured APIs with k6?](https://k6.io/blog/how-to-load-test-oauth-secured-apis-with-k6)
diff --git a/docs/sources/v0.50.x/examples/parse-html.md b/docs/sources/v0.50.x/examples/parse-html.md
new file mode 100644
index 000000000..17c8960c6
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/parse-html.md
@@ -0,0 +1,63 @@
+---
+title: 'Parse HTML'
+description: 'Scripting examples parsing HTML content.'
+weight: 06
+---
+
+# Parse HTML
+
+Examples parsing HTML content. Use the `k6/html` module for HTML parsing.
+
+| Name | Type | Description |
+| ------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------- |
+| [Selection](https://grafana.com/docs/k6//javascript-api/k6-html/selection) | Class | A jQuery-like API for accessing HTML DOM elements. |
+| [Element](https://grafana.com/docs/k6//javascript-api/k6-html/element) | Class | An HTML DOM element as returned by the Selection API. |
+| [parseHTML(src)](https://grafana.com/docs/k6//javascript-api/k6-html/parsehtml) | function | Parse an HTML string and populate a Selection object. |
+
+{{< code >}}
+
+```javascript
+import { parseHTML } from 'k6/html';
+import http from 'k6/http';
+
+export default function () {
+ const res = http.get('https://k6.io');
+ const doc = parseHTML(res.body); // equivalent to res.html()
+ const pageTitle = doc.find('head title').text();
+ const langAttr = doc.find('html').attr('lang');
+}
+```
+
+{{< /code >}}
+
+{{< code >}}
+
+```javascript
+import { parseHTML } from 'k6/html';
+import { sleep } from 'k6';
+
+export default function () {
+ const content = `
+
+ - Value term 1
+ - Value term 2
+
+ `;
+ const sel = parseHTML(content).find('dl').children();
+
+ const el1 = sel.get(0);
+ const el2 = sel.get(1);
+
+ console.log(el1.nodeName());
+ console.log(el1.id());
+ console.log(el1.textContent());
+
+ console.log(el2.nodeName());
+ console.log(el2.id());
+ console.log(el2.textContent());
+
+ sleep(1);
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/single-request.md b/docs/sources/v0.50.x/examples/single-request.md
new file mode 100644
index 000000000..eed874701
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/single-request.md
@@ -0,0 +1,24 @@
+---
+title: 'Single request'
+description: 'Example of one HTTP GET request'
+draft: 'false'
+weight: 01
+---
+
+# Single request
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+
+export const options = {
+ iterations: 1,
+};
+
+export default function () {
+ const response = http.get('https://test-api.k6.io/public/crocodiles/');
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/soap.md b/docs/sources/v0.50.x/examples/soap.md
new file mode 100644
index 000000000..4554a2e8b
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/soap.md
@@ -0,0 +1,49 @@
+---
+title: 'SOAP'
+description: 'Load Testing SOAP API.'
+weight: 14
+---
+
+# SOAP
+
+Although k6 doesn't have any built-in APIs for working with SOAP or XML data in general, you
+can still easily load test a SOAP-based API by crafting SOAP messages and using the HTTP request APIs.
+
+## Making SOAP requests
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check, sleep } from 'k6';
+
+const soapReqBody = `
+
+
+
+ UnitedStates
+
+
+`;
+
+export default function () {
+ // When making a SOAP POST request we must not forget to set the content type to text/xml
+ const res = http.post(
+ 'http://www.holidaywebservice.com/HolidayService_v2/HolidayService2.asmx',
+ soapReqBody,
+ {
+ headers: { 'Content-Type': 'text/xml' },
+ }
+ );
+
+ // Make sure the response is correct
+ check(res, {
+ 'status is 200': (r) => r.status === 200,
+ 'black friday is present': (r) => r.body.indexOf('BLACK-FRIDAY') !== -1,
+ });
+
+ sleep(1);
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/tls.md b/docs/sources/v0.50.x/examples/tls.md
new file mode 100644
index 000000000..4e97b345d
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/tls.md
@@ -0,0 +1,34 @@
+---
+title: 'Transport Layer Security (TLS)'
+description: |
+ TLS is the mechanism through which encrypted connections can be established between clients and
+ servers on the web and through which data can flow with integrity intact.
+weight: 15
+---
+
+# Transport Layer Security (TLS)
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check } from 'k6';
+
+export const options = {
+ tlsCipherSuites: ['TLS_RSA_WITH_RC4_128_SHA', 'TLS_RSA_WITH_AES_128_GCM_SHA256'],
+ tlsVersion: {
+ min: 'ssl3.0',
+ max: 'tls1.2',
+ },
+};
+
+export default function () {
+ const res = http.get('https://sha256.badssl.com');
+ check(res, {
+ 'is TLSv1.2': (r) => r.tls_version === http.TLS_1_2,
+ 'is sha256 cipher suite': (r) => r.tls_cipher_suite === 'TLS_RSA_WITH_AES_128_GCM_SHA256',
+ });
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/tracking-data-per.md b/docs/sources/v0.50.x/examples/tracking-data-per.md
new file mode 100644
index 000000000..523a5c141
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/tracking-data-per.md
@@ -0,0 +1,72 @@
+---
+title: 'Track transmitted data per URL'
+slug: '/track-transmitted-data-per-url'
+description: 'This example shows how to track data sent and received for a individual URL.'
+weight: 20
+---
+
+# Track transmitted data per URL
+
+By default, k6 collects automatically two [built-in metrics](https://grafana.com/docs/k6//using-k6/metrics#built-in-metrics) related to the transmitted data during the test execution:
+
+- `data_received`: the amount of received data.
+- `data_sent`: the amount of data sent.
+
+However, the reported values of these metrics don't tag the particular request or URL. Therefore, you cannot know the amount of data transmitted for a specific request or URL.
+
+This example shows how to track data sent and received for an individual URL.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { sleep } from 'k6';
+import { Counter } from 'k6/metrics';
+
+// Two custom metrics to track data sent and received. We will tag data points added with the corresponding URL
+// so we can filter these metrics down to see the data for individual URLs and set threshold across all or per-URL as well.
+export const epDataSent = new Counter('endpoint_data_sent');
+export const epDataRecv = new Counter('endpoint_data_recv');
+
+export const options = {
+ duration: '10s',
+ vus: 10,
+ thresholds: {
+ // We can setup thresholds on these custom metrics, "count" means bytes in this case.
+ 'endpoint_data_sent': ['count < 2048'],
+
+ // The above threshold would look at all data points added to the custom metric.
+ // If we want to only consider data points for a particular URL/endpoint we can filter by URL.
+ 'endpoint_data_sent{url:https://test.k6.io/contacts.php}': ['count < 1024'],
+ 'endpoint_data_recv{url:https://test.k6.io/}': ['count < 2048'], // "count" means bytes in this case
+ },
+};
+
+function sizeOfHeaders(hdrs) {
+ return Object.keys(hdrs).reduce((sum, key) => sum + key.length + hdrs[key].length, 0);
+}
+
+function trackDataMetricsPerURL(res) {
+ // Add data points for sent and received data
+ epDataSent.add(sizeOfHeaders(res.request.headers) + res.request.body.length, {
+ url: res.url,
+ });
+ epDataRecv.add(sizeOfHeaders(res.headers) + res.body.length, {
+ url: res.url,
+ });
+}
+
+export default function () {
+ let res;
+
+ res = http.get('https://test.k6.io/');
+ trackDataMetricsPerURL(res);
+
+ res = http.get('https://test.k6.io/contacts.php');
+ trackDataMetricsPerURL(res);
+
+ sleep(1);
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/examples/tutorials/_index.md b/docs/sources/v0.50.x/examples/tutorials/_index.md
new file mode 100644
index 000000000..5fd269862
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/tutorials/_index.md
@@ -0,0 +1,18 @@
+---
+title: 'Tutorials'
+description: 'k6 Tutorials'
+weight: 26
+---
+
+# Tutorials
+
+- [Get started with k6](https://grafana.com/docs/k6//examples/get-started-with-k6)
+- [8 basic tasks to learn k6](https://k6.io/blog/learning-js-through-load-testing/)
+- [k6 Learn](https://github.com/grafana/k6-learn)
+- [Swagger/OpenAPI integration](https://k6.io/blog/load-testing-your-api-with-swagger-openapi-and-k6)
+- [Schedule k6 tests with cron](https://k6.io/blog/performance-monitoring-with-cron-and-k6)
+- [Load test a GraphQL service](https://k6.io/blog/load-testing-graphql-with-k6)
+- [Use TypeScript in k6 scripts](https://github.com/k6io/template-typescript)
+- [Debug using a Web Proxy](https://k6.io/blog/k6-load-testing-debugging-using-a-web-proxy/)
+- [Distributed k6 tests on K8s](https://k6.io/blog/running-distributed-tests-on-k8s/)
+- [Create a k6 extension](https://k6.io/blog/extending-k6-with-xk6)
diff --git a/docs/sources/v0.50.x/examples/url-query-parameters.md b/docs/sources/v0.50.x/examples/url-query-parameters.md
new file mode 100644
index 000000000..479141204
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/url-query-parameters.md
@@ -0,0 +1,91 @@
+---
+title: 'URLs with query parameters'
+description: 'Scripting examples using URL and URLSearchParams modules.'
+weight: 21
+---
+
+# URLs with query parameters
+
+How to use **URL** and **URLSearchParams** imported from [jslib.k6.io](https://jslib.k6.io/) to construct URLs with/without query parameters.
+
+## URL
+
+{{< code >}}
+
+```javascript
+import { URL } from 'https://jslib.k6.io/url/1.0.0/index.js';
+import http from 'k6/http';
+
+export default function () {
+ const url = new URL('https://k6.io');
+
+ url.searchParams.append('utm_medium', 'organic');
+ url.searchParams.append('utm_source', 'test');
+ url.searchParams.append('multiple', ['foo', 'bar']);
+
+ const res = http.get(url.toString());
+ // https://k6.io?utm_medium=organic&utm_source=test&multiple=foo%2Cbar
+}
+```
+
+{{< /code >}}
+
+
+
+| Name | Type | Description |
+| --------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| URLSearchParams(init) | Constructor | `init` Optional: One of [USVString, sequence of pairs or record ] |
+| toString() | Method | Returns a USVString containing the whole URL |
+| hash | Property | A USVString containing a '#' followed by the fragment identifier of the URL. |
+| host | Property | A USVString containing the host, that is the `hostname`, and then, if the port of the URL is nonempty, a ':', followed by the `port` of the URL. |
+| hostname | Property | A USVString containing the [domain name](https://developer.mozilla.org/en-US/docs/Glossary/Domain_name) of the URL. |
+| href | Property | Returns a USVString containing the whole URL. |
+| origin | Property | Returns a USVString containing the origin of the URL, that is its scheme, its domain and its port. |
+| password | Property | A USVString containing the password specified before the domain name. |
+| pathname | Property | Is a USVString containing an initial '/' followed by the path of the URL, not including the query string or fragment. |
+| port | Property | A USVString containing the port number of the URL. |
+| protocol | Property | A USVString containing the protocol scheme of the URL, including the final ':'. |
+| search | Property | A USVString indicating the URL's parameter string; if any parameters are provided, this string includes all of them, beginning with the leading ? character. |
+| searchParams | Property | A [URLSearchParams](#urlsearchparams) object which can be used to access the individual query parameters found in search. |
+| username | Property | A USVString containing the username specified before the domain name. |
+
+
+
+## URLSearchParams
+
+{{< code >}}
+
+```javascript
+import { URLSearchParams } from 'https://jslib.k6.io/url/1.0.0/index.js';
+import http from 'k6/http';
+
+export default function () {
+ const searchParams = new URLSearchParams([
+ ['utm_medium', 'organic'],
+ ['utm_source', 'test'],
+ ['multiple', 'foo'],
+ ['multiple', 'bar'],
+ ]);
+
+ const res = http.get(`${'https://k6.io'}?${searchParams.toString()}`);
+ // https://k6.io?utm_medium=organic&utm_source=test&multiple=foo&multiple=bar
+}
+```
+
+{{< /code >}}
+
+| Name | Type | Description |
+| --------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| URLSearchParams(init) | Constructor | `init` Optional: One of [USVString, sequence of pairs or record ] |
+| toString() | Method | Returns a query string (questionmark is omitted). |
+| append() | Method | Appends a specified key/value pair as a new search parameter. |
+| delete() | Method | Deletes the given search parameter, and its associated value, from the list of all search parameters. |
+| entries() | Method | Returns an iterator allowing iteration through all key/value pairs contained in this object. |
+| forEach() | Method | Allows iteration through all values contained in this object via a callback function. `callback(value, key)`. |
+| get() | Method | Returns the first value associated with the given search parameter. |
+| getAll() | Method | Returns all the values associated with a given search parameter. |
+| has() | Method | Returns a Boolean that indicates whether a parameter with the specified name exists. |
+| keys() | Method | Returns an iterator allowing iteration through all keys of the key/value pairs contained in this object. |
+| values() | Method | Returns an iterator allowing iteration through all values of the key/value pairs contained in this object. |
+| set() | Method | Sets the value associated with a given search parameter to the given value. If there were several matching values, this method deletes the others. If the search parameter doesn't exist, this method creates it. |
+| sort() | Method | Sorts all key/value pairs, if any, by their keys. |
diff --git a/docs/sources/v0.50.x/examples/websockets.md b/docs/sources/v0.50.x/examples/websockets.md
new file mode 100644
index 000000000..6db2b362d
--- /dev/null
+++ b/docs/sources/v0.50.x/examples/websockets.md
@@ -0,0 +1,84 @@
+---
+title: 'WebSockets'
+description: |
+ Scripting example on how to use WebSocket API in k6.
+weight: 13
+---
+
+# WebSockets
+
+Here's a load test for CrocoChat - a WebSocket chat API available on [https://test-api.k6.io/](https://test-api.k6.io/).
+
+Multiple VUs join a chat room and discuss various things for up to 1 minute, after which they disconnect.
+
+Each VU receives messages sent by all chat participants.
+
+{{< code >}}
+
+```javascript
+import { randomString, randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
+import ws from 'k6/ws';
+import { check, sleep } from 'k6';
+
+const sessionDuration = randomIntBetween(10000, 60000); // user session between 10s and 1m
+const chatRoomName = 'publicRoom'; // choose your chat room name
+
+export const options = {
+ vus: 10,
+ iterations: 10,
+};
+
+export default function () {
+ const url = `wss://test-api.k6.io/ws/crocochat/${chatRoomName}/`;
+ const params = { tags: { my_tag: 'my ws session' } };
+
+ const res = ws.connect(url, params, function (socket) {
+ socket.on('open', function open() {
+ console.log(`VU ${__VU}: connected`);
+
+ socket.send(JSON.stringify({ event: 'SET_NAME', new_name: `Croc ${__VU}` }));
+
+ socket.setInterval(function timeout() {
+ socket.send(JSON.stringify({ event: 'SAY', message: `I'm saying ${randomString(5)}` }));
+ }, randomIntBetween(2000, 8000)); // say something every 2-8seconds
+ });
+
+ socket.on('ping', function () {
+ console.log('PING!');
+ });
+
+ socket.on('pong', function () {
+ console.log('PONG!');
+ });
+
+ socket.on('close', function () {
+ console.log(`VU ${__VU}: disconnected`);
+ });
+
+ socket.on('message', function (message) {
+ const msg = JSON.parse(message);
+ if (msg.event === 'CHAT_MSG') {
+ console.log(`VU ${__VU} received: ${msg.user} says: ${msg.message}`);
+ } else if (msg.event === 'ERROR') {
+ console.error(`VU ${__VU} received:: ${msg.message}`);
+ } else {
+ console.log(`VU ${__VU} received unhandled message: ${msg.message}`);
+ }
+ });
+
+ socket.setTimeout(function () {
+ console.log(`VU ${__VU}: ${sessionDuration}ms passed, leaving the chat`);
+ socket.send(JSON.stringify({ event: 'LEAVE' }));
+ }, sessionDuration);
+
+ socket.setTimeout(function () {
+ console.log(`Closing the socket forcefully 3s after graceful LEAVE`);
+ socket.close();
+ }, sessionDuration + 3000);
+ });
+
+ check(res, { 'Connected successfully': (r) => r && r.status === 101 });
+}
+```
+
+{{< /code >}}
diff --git a/docs/sources/v0.50.x/extensions/_index.md b/docs/sources/v0.50.x/extensions/_index.md
new file mode 100644
index 000000000..83e2fc7cb
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/_index.md
@@ -0,0 +1,80 @@
+---
+title: Extensions
+description: 'The k6 extension ecosystem enables developers and testers to extend k6 to cover use cases not supported natively in the core. Explore the endless possibilities of k6 and xk6.'
+weight: 09
+---
+
+# Extensions
+
+Expand the potential use cases for k6.
+
+## Quickstart
+
+
+
+## Custom k6 builds
+
+With k6 extensions, you can create custom k6 binaries to support your specific reliability-testing needs.
+
+Currently, k6 supports two ways to extend its native functionality:
+
+- **JavaScript extensions** extend the JavaScript APIs available to your test scripts. Add support for new network protocols, improve performance compared to equivalent JS libraries, or add features.
+
+- **Output extensions** send metrics to a custom file format or service. Add custom processing and dispatching.
+
+## xk6 makes custom binaries
+
+[xk6](https://github.com/grafana/xk6/) is command-line tool and framework written in Go. With xk6, you build custom k6 binaries that bundle one or more extensions written in Go. You have two options for creating k6 binaries: using [Go and xk6](https://grafana.com/docs/k6//extensions/build-k6-binary-using-go/) or [Docker](https://grafana.com/docs/k6//extensions/build-k6-binary-using-docker/):
+
+{{< code >}}
+
+```go-and-xk6
+xk6 build \
+ --with github.com/grafana/xk6-sql@v0.0.1 \
+ --with github.com/grafana/xk6-output-prometheus-remote
+```
+
+```docker-in-linux
+docker run --rm -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" grafana/xk6 build \
+ --with github.com/grafana/xk6-sql@v0.0.1 \
+ --with github.com/grafana/xk6-output-prometheus-remote
+```
+
+{{< /code >}}
+
+
+
+## Extension use cases
+
+The extensions ecosystem provides endless possibilities to expand the functionality for your k6 testing. Some reasons you might want to extend k6 include the following:
+
+- **To add support for a new network protocol**
+
+ For example, [xk6-amqp](https://github.com/grafana/xk6-amqp) gives access to resources using the Advanced Message Queueing Protocol (AMQP). With xk6-amqp, your scripts can create message queues and seed messages for tests that include systems like RabbitMQ or ActiveMQ (among others).
+
+- **To incorporate a client library for test dependency**
+
+ Everyone wants to run their services in Kubernetes these days. With [xk6-kubernetes](https://github.com/grafana/xk6-kubernetes), your JavaScript tests can interface directly with Kubernetes resources using functionality typically restricted to kubectl. Prepare isolated Namespaces for each test run, or inject environment variables as ConfigMaps.
+
+- **To format and send metrics to the output of your choice**
+
+ Suppose your company has consolidated its observability metrics into Prometheus. Use [xk6-output-prometheus-remote](https://github.com/grafana/xk6-output-prometheus-remote) to publish your k6 test metrics to Prometheus as well!
+
+- **To improve script performance and efficiency**
+
+ Perhaps your company uses OpenTelemetry to trace service requests through layers of microservices. Using [xk6-distributed-tracing](https://github.com/grafana/xk6-distributed-tracing), you can update the http client to link your test requests as the origin for your traces—no need to add JavaScript code to supply the required trace headers.
+
+Next, [Explore the available extensions](https://grafana.com/docs/k6//extensions/explore) to see how you can expand your use of k6 right now.
diff --git a/docs/sources/v0.50.x/extensions/build-k6-binary-using-docker.md b/docs/sources/v0.50.x/extensions/build-k6-binary-using-docker.md
new file mode 100644
index 000000000..e3d6aefea
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/build-k6-binary-using-docker.md
@@ -0,0 +1,180 @@
+---
+title: 'Build a k6 binary using Docker'
+description: 'Guide to build a k6 binary with extensions using Docker.'
+weight: 03
+---
+
+# Build a k6 binary using Docker
+
+Using the [xk6 Docker image](https://hub.docker.com/r/grafana/xk6/) can simplify the process of creating a custom k6 binary. It avoids having to setup a local Go environment, and install xk6 manually.
+
+{{% admonition type="note" %}}
+
+This tutorial is about creating a custom k6 binary (using Docker). If you want to create a Docker image with a custom k6 binary, refer instead to [Using modules with Docker](https://grafana.com/docs/k6//using-k6/modules/#using-modules-with-docker).
+
+{{% /admonition %}}
+
+## Building your first extension
+
+For example, to build a custom k6 binary with the latest versions of k6 and the [`xk6-kafka`](https://github.com/mostafa/xk6-kafka) and [`xk6-output-influxdb`](https://github.com/grafana/xk6-output-influxdb) extensions, run one of the commands below, depending on your operating system:
+
+{{< code >}}
+
+```linux
+docker run --rm -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" grafana/xk6 build \
+ --with github.com/mostafa/xk6-kafka \
+ --with github.com/grafana/xk6-output-influxdb
+```
+
+```mac
+docker run --rm -e GOOS=darwin -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" \
+ grafana/xk6 build \
+ --with github.com/mostafa/xk6-kafka \
+ --with github.com/grafana/xk6-output-influxdb
+```
+
+```windows-powershell
+docker run --rm -e GOOS=windows -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" `
+ grafana/xk6 build --output k6.exe `
+ --with github.com/mostafa/xk6-kafka `
+ --with github.com/grafana/xk6-output-influxdb
+```
+
+```windows
+docker run --rm -e GOOS=windows -v "%cd%:/xk6" ^
+ grafana/xk6 build --output k6.exe ^
+ --with github.com/mostafa/xk6-kafka ^
+ --with github.com/grafana/xk6-output-influxdb
+```
+
+{{< /code >}}
+
+This creates a `k6` (or `k6.exe`) binary in the current working directory.
+
+To build the binary with concrete versions, see the example below (k6 `v0.45.1`, xk6-kafka `v0.19.1`, and xk6-output-influxdb `v0.4.1`):
+
+{{< code >}}
+
+```linux
+docker run --rm -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" grafana/xk6 build v0.45.1 \
+ --with github.com/mostafa/xk6-kafka@v0.19.1 \
+ --with github.com/grafana/xk6-output-influxdb@v0.4.1
+```
+
+```mac
+docker run --rm -e GOOS=darwin -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" \
+ grafana/xk6 build v0.45.1 \
+ --with github.com/mostafa/xk6-kafka@v0.19.1 \
+ --with github.com/grafana/xk6-output-influxdb@v0.4.1
+```
+
+```windows-powershell
+docker run --rm -e GOOS=windows -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" `
+ grafana/xk6 build v0.45.1 --output k6.exe `
+ --with github.com/mostafa/xk6-kafka@v0.19.1 `
+ --with github.com/grafana/xk6-output-influxdb@v0.4.1
+```
+
+```windows
+docker run --rm -e GOOS=windows -v "%cd%:/xk6" ^
+ grafana/xk6 build v0.45.1 --output k6.exe ^
+ --with github.com/mostafa/xk6-kafka@v0.19.1 ^
+ --with github.com/grafana/xk6-output-influxdb@v0.4.1
+```
+
+{{< /code >}}
+
+## Breaking down the command
+
+The example command line may look a bit intimidating at first, but let's focus on the first part, which pertains strictly to Docker:
+
+```bash
+docker run --rm -u "$(id -u):$(id -g)" -v "${PWD}:/xk6"
+```
+
+This tells Docker to run a new container from an image.
+
+- `--rm` means the container will be destroyed once your build is completed.
+- `-u` specifies the user and group IDs of the account on the host machine. This is important for the `k6` file to have the same file permissions as the host user.
+- `-v` is required to mount the current working directory inside the container, so that the `k6` binary can be written to it.
+
+For Windows and Mac, we additionally include the target system as an environment variable:
+
+```bash
+-e GOOS=
+```
+
+The remainder is straight from the [xk6 documentation](https://github.com/grafana/xk6/#command-usage), with the exception that we use the `grafana/xk6` _image_ rather than a local installation of `xk6`:
+
+{{< code >}}
+
+```plain
+grafana/xk6 build []
+ [--output ]
+ [--with ...]
+ [--replace ...]
+
+Flags:
+ --output specifies the new binary name [default: 'k6']
+ --replace enables override of dependencies for k6 and extensions [default: none]
+ --with the extension module to be included in the binary [default: none]
+```
+
+{{< /code >}}
+
+{{% admonition type="caution" %}}
+
+The use of `--replace` should be considered an advanced feature to be avoided unless required.
+
+{{% /admonition %}}
+
+Referring back to our executed command, note that:
+
+- We specify the version as `v0.43.1`. When you omit the version or specify `latest`, you build using the _latest_ source code for k6.
+ Consider using a stable [release version](https://github.com/grafana/k6/releases) as a best practice unless you genuinely want the _bleeding edge_.
+- We specify a full GitHub URI for the extension repository with each `--with`.
+ If a version is not specified, the default is again the `latest`.
+ Check your extension repository for stable release versions, if available, to lock in your version as we've done with `xk6-kafka@v0.17.0` and `xk6-output-influxdb@v0.3.0`.
+- For Windows, we used the `--output` option to name our result as `k6.exe`; if not specified, our new binary is `k6` within the current directory.
+ If you specify a directory, the new binary will be `k6` within _that_ directory.
+ If you specify a path to a non-existent file, e.g. `/tmp/k6-extended`, this will be the path and filename for the binary.
+
+Run `./k6 version` (or `k6.exe version`) to check that your build is based on the appropriate `k6` version and contains the desired extensions. For example:
+
+{{< code >}}
+
+```bash
+$ ./k6 version
+k6 v0.43.1 ((devel), go1.20.1, darwin/amd64)
+Extensions:
+ github.com/grafana/xk6-output-influxdb v0.3.0, xk6-influxdb [output]
+ github.com/mostafa/xk6-kafka v0.17.0, k6/x/kafka [js]
+```
+
+{{< /code >}}
+
+## Running your extended binary
+
+Now that we have our newly built k6 binary, we can run scripts using the functionalities
+of the bundled extensions.
+
+{{< code >}}
+
+```bash
+./k6 run my-script.js
+```
+
+```batch
+k6.exe run my-script.js
+```
+
+{{< /code >}}
+
+> Be sure to specify the binary just built in the current directory as `./k6`, or else
+> Linux/Mac could execute another k6 binary on your system path. Windows shells will
+> first search for the binary in the current directory by default.
+
+## Encountering issues?
+
+If you're having issues, search the [k6 Community Forum](https://community.grafana.com/c/grafana-k6/extensions/82).
+Someone may have had the same issue in the past.
diff --git a/docs/sources/v0.50.x/extensions/build-k6-binary-using-go.md b/docs/sources/v0.50.x/extensions/build-k6-binary-using-go.md
new file mode 100644
index 000000000..fdc921309
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/build-k6-binary-using-go.md
@@ -0,0 +1,126 @@
+---
+title: 'Build a k6 binary using Go'
+description: 'Guide to build a k6 binary that includes one or many extensions using xk6.'
+weight: 02
+---
+
+# Build a k6 binary using Go
+
+To use an extension that you found on the [Extension page](https://grafana.com/docs/k6//extensions/explore) or the [xk6 GitHub topic](https://github.com/topics/xk6),
+you need to build a binary using Go.
+
+{{% admonition type="note" %}}
+
+Not interested in setting up a Go environment? You can [use Docker instead](https://grafana.com/docs/k6//extensions/build-k6-binary-using-docker/).
+
+{{% /admonition %}}
+
+## Before you start
+
+- Set up [Go](https://golang.org/doc/install) and [Git](https://git-scm.com/).
+- Make sure that your `$PATH` environment variable is updated so that `go version` returns the correct version.
+
+## Installing xk6
+
+Given the prerequisite Go setup, installing [xk6](https://github.com/grafana/xk6) itself requires only the following command:
+
+```bash
+$ go install go.k6.io/xk6/cmd/xk6@latest
+```
+
+To confirm your installation, run `which xk6` on Linux and Mac, or `where xk6` on Windows.
+Make sure the command returns a valid path.
+
+If not, check that you've correctly defined the`$GOPATH` environment variable and that `$GOPATH/bin`
+is in your `$PATH`. See the [Go documentation](https://golang.org/cmd/go/#hdr-GOPATH_environment_variable)
+for details.
+
+## Building your first extension
+
+Once installed, building a k6 binary with one or more extensions can be done with the `xk6 build`
+command as follows:
+
+```bash
+$ xk6 build latest \
+ --with github.com/grafana/xk6-sql@v0.0.1 \
+ --with github.com/grafana/xk6-output-prometheus-remote
+```
+
+Once completed, the **current directory** contains your newly built `k6` binary with
+the [`xk6-sql`](https://github.com/grafana/xk6-sql) and [`xk6-output-prometheus-remote`](https://github.com/grafana/xk6-output-prometheus-remote)
+extensions.
+
+```bash
+... [INFO] Build environment ready
+... [INFO] Building k6
+... [INFO] Build complete: ./k6
+
+xk6 has now produced a new k6 binary which may be different than the command on your system path!
+Be sure to run './k6 run ' from the '...' directory.
+```
+
+## Breaking down the xk6 command
+
+From the [xk6 documentation](https://github.com/grafana/xk6/#command-usage), the command-line usage is as follows:
+
+```plain
+xk6 build []
+ [--output ]
+ [--with ...]
+ [--replace ...]
+
+Flags:
+ --output specifies the new binary name [default: 'k6']
+ --replace enables override of dependencies for k6 and extensions [default: none]
+ --with the extension module to be included in the binary [default: none]
+```
+
+> The use of `--replace` should be considered an advanced feature to be avoided unless required.
+
+Referring back to our executed command, note that:
+
+- We specify the version as `latest`, which is also the default if we hadn't supplied
+ a version. `latest` means that we'll build using the _latest_ source code for k6. Consider using
+ a stable [release version](https://github.com/grafana/k6/releases) as a best practice unless
+ you genuinely want the _bleeding edge_.
+- With each `--with`, we specified a full GitHub URI for the extension repository. If not specifying
+ a version, the default is `latest` once again. Check your extension repository for stable
+ release versions, if available, to lock in your version as we've done with `xk6-sql@v0.0.1`.
+- We did not use the `--output` option; therefore, our new binary is `k6` within the current directory.
+ Had we used `--output k6-extended`, our binary would be named `k6-extended` within the current
+ directory. If a directory is specified, then the new binary would be `k6` within
+ _that_ directory. If a path to a non-existent file, e.g. `/tmp/k6-extended`, this will be the
+ path and filename for the binary.
+
+Running `./k6 version` should show your build is based upon the appropriate version.
+
+## Building from a local repository
+
+Suppose now you've cloned the `xk6-sql` repository and want the local version included in your
+custom binary? From the cloned directory, we would then use:
+
+```bash
+--with github.com/grafana/xk6-sql=.
+```
+
+Based upon the command usage described in the previous section, this tells xk6 to use
+the _current directory_ as the _replacement_ for the _module_.
+
+## Running your extended binary
+
+Now that we have our newly built k6 binary, we can run scripts using the functionalities
+of the bundled extensions.
+
+```bash
+$ ./k6 run my-script.js
+```
+
+> Be sure to specify the binary just built in the current directory as `./k6`, or else
+> Linux/Mac could execute another k6 binary on your system path. Windows shells will
+> first search for the binary in the current directory by default.
+
+
diff --git a/docs/sources/v0.50.x/extensions/create/_index.md b/docs/sources/v0.50.x/extensions/create/_index.md
new file mode 100644
index 000000000..717c37bff
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/create/_index.md
@@ -0,0 +1,43 @@
+---
+title: 'Create a k6 extension'
+menuTitle: 'Create an extension'
+description: 'Creating k6 extensions does not have to be a daunting task, but there are some prerequisites to succeed.'
+weight: 04
+---
+
+# Create a k6 extension
+
+If you find a gap in your testing process that no k6 extension can fix,
+consider building your own extension.
+
+These tutorials show you how to create custom JavaScript and output extensions.
+
+- [Create a JavaScript extension](https://grafana.com/docs/k6//extensions/create/javascript-extensions) to extend the JavaScript functionality of your script or add support for a new network protocol to test.
+- [Create an Output extension](https://grafana.com/docs/k6//extensions/create/output-extensions) to process the metrics emitted by k6 or publish them to unsupported backend stores.
+
+## Necessary knowledge
+
+Anyone who can use the command line to edit files and install software should be able to follow along.
+But, if you want to create an extension for more than the purposes of demonstration,
+there's some background knowledge you should have:
+
+- You should be familiar with both Go(lang), JavaScript, and their tooling
+- You should understand how the [_Go-to-JavaScript_](https://grafana.com/docs/k6//extensions/explanations/go-js-bridge) bridge works within k6
+
+{{% admonition type="note" %}}
+
+If you maintain a public xk6 repository and wish to have your extension listed in our [registry](https://grafana.com/docs/k6//extensions/explore),
+be sure to review the [requirements](https://grafana.com/docs/k6//extensions/explanations/extensions-registry#registry-requirements).
+
+{{% /admonition %}}
+
+## Avoid unneeded work
+
+These actions may save you the trouble of building a whole new extension when its not needed.
+
+- Confirm that a similar extension doesn't already exist for your use case. Take a look at
+ the [Extensions listing](https://grafana.com/docs/k6//extensions/explore) and the [`xk6` topic on GitHub](https://github.com/topics/xk6).
+- Prefer generic solutions. For example, if you can test a system with a generic protocol like _MQTT_, prefer
+ [xk6-mqtt](https://github.com/pmalhaire/xk6-mqtt) over a new extension for some custom protocol.
+- Lean toward writing pure JavaScript libraries over building an extension in Go.
+ A JavaScript library will be better supported, more straightforward, and reusable than an extension.
diff --git a/docs/sources/v0.50.x/extensions/create/javascript-extensions.md b/docs/sources/v0.50.x/extensions/create/javascript-extensions.md
new file mode 100644
index 000000000..0841d9b1f
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/create/javascript-extensions.md
@@ -0,0 +1,348 @@
+---
+title: 'JavaScript Extensions'
+description: 'Follow these steps to build a JS extension for k6.'
+weight: 01
+---
+
+# JavaScript Extensions
+
+Take advantage of Go's speed, power, and efficiency while providing the flexibility of using JavaScript APIs
+within your test scripts.
+
+By implementing k6 interfaces, you can close various gaps in your testing setup:
+
+- New network protocols
+- Improved performance
+- Features not supported by k6 core
+
+## Before you start
+
+To run this tutorial, you'll need the following applications installed:
+
+- Go
+- Git
+
+You also need to install xk6:
+
+```bash
+$ go install go.k6.io/xk6/cmd/xk6@latest
+```
+
+## Write a simple extension
+
+1. First, set up a directory to work in:
+
+```bash
+$ mkdir xk6-compare; cd xk6-compare; go mod init xk6-compare
+```
+
+1. In the directory, make a Go file for your JavaScript extension.
+
+A simple JavaScript extension requires a struct that exposes methods called by the test script.
+
+
+
+```go
+package compare
+
+import "fmt"
+
+// Compare is the type for our custom API.
+type Compare struct{
+ ComparisonResult string // textual description of the most recent comparison
+}
+
+// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
+func (c *Compare) IsGreater(a, b int) bool {
+ if a > b {
+ c.ComparisonResult = fmt.Sprintf("%d is greater than %d", a, b)
+ return true
+ } else {
+ c.ComparisonResult = fmt.Sprintf("%d is NOT greater than %d", a, b)
+ return false
+ }
+}
+```
+
+1. Register the module to use these from k6 test scripts.
+
+{{% admonition type="note" %}}
+
+k6 extensions must have the `k6/x/` prefix,
+and the short name must be unique among all extensions built in the same k6 binary.
+
+{{% /admonition %}}
+
+```go
+import "go.k6.io/k6/js/modules"
+
+// init is called by the Go runtime at application startup.
+func init() {
+ modules.Register("k6/x/compare", new(Compare))
+}
+```
+
+1. Save the file as something like `compare.go`. The final code looks like this:
+
+{{< code >}}
+
+```go
+package compare
+
+import (
+ "fmt"
+ "go.k6.io/k6/js/modules"
+)
+
+// init is called by the Go runtime at application startup.
+func init() {
+ modules.Register("k6/x/compare", new(Compare))
+}
+
+// Compare is the type for our custom API.
+type Compare struct{
+ ComparisonResult string // textual description of the most recent comparison
+}
+
+// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
+func (c *Compare) IsGreater(a, b int) bool {
+ if a > b {
+ c.ComparisonResult = fmt.Sprintf("%d is greater than %d", a, b)
+ return true
+ } else {
+ c.ComparisonResult = fmt.Sprintf("%d is NOT greater than %d", a, b)
+ return false
+ }
+}
+```
+
+{{< /code >}}
+
+## Compile your extended k6
+
+To build a k6 binary with this extension, run this command:
+
+```bash
+$ xk6 build --with xk6-compare=.
+```
+
+{{% admonition type="note" %}}
+
+When building from source code, `xk6-compare` is the Go module name passed to `go mod init`.
+Usually, this would be a URL similar to `github.com/grafana/xk6-compare`.
+
+{{% /admonition %}}
+
+## Use your extension
+
+Now, use the extension in a test script!
+
+1. Make a file with a name like `test.js` then add this code:
+
+{{< code >}}
+
+```javascript
+import compare from 'k6/x/compare';
+
+export default function () {
+ console.log(`${compare.isGreater(2, 1)}, ${compare.comparison_result}`);
+ console.log(`${compare.isGreater(1, 3)}, ${compare.comparison_result}`);
+}
+```
+
+{{< /code >}}
+
+1. Run the test with `./k6 run test.js`.
+
+It should output the following:
+
+```shell
+INFO[0000] true, 2 is greater than 1 source=console
+INFO[0000] false, 1 is NOT greater than 3 source=console
+```
+
+## Use the advanced module API
+
+Suppose your extension needs access to internal k6 objects to, for example, inspect the state of the test during execution.
+We will need to make slightly more complicated changes to the above example.
+
+Our main `Compare` struct should implement the [`modules.Instance`](https://pkg.go.dev/go.k6.io/k6/js/modules#Instance) interface
+to access the [`modules.VU`](https://pkg.go.dev/go.k6.io/k6/js/modules#VU) to inspect internal k6 objects such as:
+
+- [`lib.State`](https://pkg.go.dev/go.k6.io/k6/lib#State), the VU state with values like the VU ID and iteration number
+- [`goja.Runtime`](https://pkg.go.dev/github.com/dop251/goja#Runtime), the JavaScript runtime used by the VU
+- a global `context.Context` containing objects like the [`lib.ExecutionState`](https://pkg.go.dev/go.k6.io/k6/lib#ExecutionState)
+
+Additionally, there should be a root module implementation of the [`modules.Module`](https://pkg.go.dev/go.k6.io/k6/js/modules#Module)
+interface to serve as a factory of `Compare` instances for each VU.
+
+{{% admonition type="caution" %}}
+
+The significance depends on the size of your module.
+
+{{% /admonition %}}
+
+Here's what that would look like:
+
+{{< code >}}
+
+```go
+package compare
+
+import (
+ "fmt"
+ "go.k6.io/k6/js/modules"
+)
+
+// init is called by the Go runtime at application startup.
+func init() {
+ modules.Register("k6/x/compare", New())
+}
+
+type (
+ // RootModule is the global module instance that will create module
+ // instances for each VU.
+ RootModule struct{}
+
+ // ModuleInstance represents an instance of the JS module.
+ ModuleInstance struct {
+ // vu provides methods for accessing internal k6 objects for a VU
+ vu modules.VU
+ // comparator is the exported type
+ comparator *Compare
+ }
+)
+
+// Ensure the interfaces are implemented correctly.
+var (
+ _ modules.Instance = &ModuleInstance{}
+ _ modules.Module = &RootModule{}
+)
+
+// New returns a pointer to a new RootModule instance.
+func New() *RootModule {
+ return &RootModule{}
+}
+
+// NewModuleInstance implements the modules.Module interface returning a new instance for each VU.
+func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
+ return &ModuleInstance{
+ vu: vu,
+ comparator: &Compare{vu: vu},
+ }
+}
+
+// Compare is the type for our custom API.
+type Compare struct{
+ vu modules.VU // provides methods for accessing internal k6 objects
+ ComparisonResult string // textual description of the most recent comparison
+}
+
+// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
+func (c *Compare) IsGreater(a, b int) bool {
+ if a > b {
+ c.ComparisonResult = fmt.Sprintf("%d is greater than %d", a, b)
+ return true
+ } else {
+ c.ComparisonResult = fmt.Sprintf("%d is NOT greater than %d", a, b)
+ return false
+ }
+}
+
+// Exports implements the modules.Instance interface and returns the exported types for the JS module.
+func (mi *ModuleInstance) Exports() modules.Exports {
+ return modules.Exports{
+ Default: mi.comparator,
+ }
+}
+```
+
+{{< /code >}}
+
+{{% admonition type="note" %}}
+
+Notice that we implemented the Module API and now `modules.Register` the _root module_ rather than our _Compare_ object!
+
+{{% /admonition %}}
+
+## Accessing runtime state
+
+At this time, we've provided access to the [`modules.VU`](https://pkg.go.dev/go.k6.io/k6/js/modules#VU) from the `Compare`
+type; however, we aren't taking advantage of the methods provided. Here is a contrived example of how we can utilize the
+runtime state:
+
+{{< code >}}
+
+```go
+// InternalState holds basic metadata from the runtime state.
+type InternalState struct {
+ ActiveVUs int64 `js:"activeVUs"`
+ Iteration int64
+ VUID uint64 `js:"vuID"`
+ VUIDFromRuntime goja.Value `js:"vuIDFromRuntime"`
+}
+
+// GetInternalState interrogates the current virtual user for state information.
+func (c *Compare) GetInternalState() *InternalState {
+ state := c.vu.State()
+ ctx := c.vu.Context()
+ es := lib.GetExecutionState(ctx)
+ rt := c.vu.Runtime()
+
+ return &InternalState{
+ VUID: state.VUID,
+ VUIDFromRuntime: rt.Get("__VU"),
+ Iteration: state.Iteration,
+ ActiveVUs: es.GetCurrentlyActiveVUsCount(),
+ }
+}
+```
+
+{{< /code >}}
+
+Create a test script to utilize the new `getInternalState()` function as in the following:
+
+{{< code >}}
+
+```javascript
+import compare from 'k6/x/compare';
+
+export default function () {
+ const state = compare.getInternalState();
+ console.log(
+ `Active VUs: ${state.activeVUs}, Iteration: ${state.iteration}, VU ID: ${state.vuID}, VU ID from runtime: ${state.vuIDFromRuntime}`
+ );
+}
+```
+
+{{< /code >}}
+
+Executing the script as `./k6 run test-state.js --vus 2 --iterations 5` will produce output similar to the following:
+
+```shell
+INFO[0000] Active VUs: 2, Iteration: 0, VU ID: 2, VU ID from runtime: 2 source=console
+INFO[0000] Active VUs: 2, Iteration: 0, VU ID: 1, VU ID from runtime: 1 source=console
+INFO[0000] Active VUs: 2, Iteration: 1, VU ID: 2, VU ID from runtime: 2 source=console
+INFO[0000] Active VUs: 2, Iteration: 1, VU ID: 1, VU ID from runtime: 1 source=console
+INFO[0000] Active VUs: 2, Iteration: 2, VU ID: 2, VU ID from runtime: 2 source=console
+```
+
+> For a more extensive usage example of this API, look at the
+> [`k6/execution`](https://github.com/grafana/k6/blob/master/js/modules/k6/execution/execution.go) module.
+
+## Things to keep in mind
+
+- The code in the `default` function (or another function specified by
+ [`exec`](https://grafana.com/docs/k6//using-k6/scenarios#options)) will be executed many
+ times during a test run and possibly in parallel by thousands of VUs.
+ Any operation of your extension should therefore be performant
+ and [thread-safe](https://en.wikipedia.org/wiki/Thread_safety).
+- Any _heavy_ initialization should be done in the [init context](https://grafana.com/docs/k6//javascript-api/init-context),
+ if possible, and not as part of the `default` function execution.
+- Use the registry's [`NewMetric`](https://pkg.go.dev/go.k6.io/k6/metrics#Registry.NewMetric) method to create
+ custom metrics; to emit them, use [`metrics.PushIfNotDone()`](https://pkg.go.dev/go.k6.io/k6/metrics#PushIfNotDone).
+
+> Questions? Feel free to join the discussion on extensions in the [k6 Community Forum](https://community.grafana.com/c/grafana-k6/extensions/82).
+
+Next, create an [Output extension](https://grafana.com/docs/k6//extensions/create/output-extensions) to publish test metrics
+to a destination not already supported by k6.
diff --git a/docs/sources/v0.50.x/extensions/create/output-extensions.md b/docs/sources/v0.50.x/extensions/create/output-extensions.md
new file mode 100644
index 000000000..d20910642
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/create/output-extensions.md
@@ -0,0 +1,248 @@
+---
+title: 'Output Extensions'
+description: 'Follow these steps to build an output extension for k6.'
+weight: 02
+---
+
+# Output Extensions
+
+k6 provides many [metrics](https://grafana.com/docs/k6//using-k6/metrics) and [output formats](https://grafana.com/docs/k6//results-output/), but it cannot directly support all possibilities.
+To store or alter metrics captured during an active k6 test,
+you can create a custom output extension.
+
+Output extension binaries can use the `--out` flag to send metrics to a custom place.
+Some potential reasons for a custom extension could include:
+
+- To support a time-series database not already supported
+- To add derived metrics data for storage
+- To filter metrics to only the data you care about
+
+Like [JavaScript extensions](https://grafana.com/docs/k6//extensions/create/javascript-extensions),
+output extensions rely on the extension author to implement specific APIs.
+
+## Before you start:
+
+To run this tutorial, you'll need the following applications installed:
+
+- Go
+- Git
+
+You also need to install xk6:
+
+```bash
+$ go install go.k6.io/xk6/cmd/xk6@latest
+```
+
+## Write a simple extension
+
+1. Set up a directory to work in.
+
+```bash
+$ mkdir xk6-output-logger; cd xk6-output-logger; go mod init xk6-output-logger
+```
+
+1. The core of an Output extension is a struct that implements the [`output.Output`](https://pkg.go.dev/go.k6.io/k6/output#Output)
+ interface.
+
+Create a simple example that outputs each set of metrics to the console as received by the `AddMetricSamples(samples []metrics.SampleContainer)`
+method of the output interface.
+
+```go
+package log
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "go.k6.io/k6/metrics"
+ "go.k6.io/k6/output"
+)
+
+// AddMetricSamples receives metric samples from the k6 Engine as they're emitted.
+func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
+ for _, sample := range samples {
+ all := sample.GetSamples()
+ fmt.Fprintf(l.out, "%s [%s]\n", all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
+ }
+}
+
+// metricKeyValues returns a string of key-value pairs for all metrics in the sample.
+func metricKeyValues(samples []metrics.Sample) string {
+ names := make([]string, 0, len(samples))
+ for _, sample := range samples {
+ names = append(names, fmt.Sprintf("%s=%v", sample.Metric.Name, sample.Value))
+ }
+ return strings.Join(names, ", ")
+}
+```
+
+1. Register the module to use these from k6 test scripts.
+
+```go
+import "go.k6.io/k6/output"
+
+// init is called by the Go runtime at application startup.
+func init() {
+ output.RegisterExtension("logger", New)
+}
+```
+
+{{% admonition type="note" %}}
+
+You must use the registered with the `-o`, or `--out` flag when running k6!
+
+{{% /admonition %}}
+
+The final extension code looks like this:
+
+{{< code >}}
+
+```go
+package log
+
+import (
+ "fmt"
+ "io"
+ "strings"
+ "time"
+
+ "go.k6.io/k6/metrics"
+ "go.k6.io/k6/output"
+)
+
+// init is called by the Go runtime at application startup.
+func init() {
+ output.RegisterExtension("logger", New)
+}
+
+// Logger writes k6 metric samples to stdout.
+type Logger struct {
+ out io.Writer
+}
+
+// New returns a new instance of Logger.
+func New(params output.Params) (output.Output, error) {
+ return &Logger{params.StdOut}, nil
+}
+
+// Description returns a short human-readable description of the output.
+func (*Logger) Description() string {
+ return "logger"
+}
+
+// Start initializes any state needed for the output, establishes network
+// connections, etc.
+func (o *Logger) Start() error {
+ return nil
+}
+
+// AddMetricSamples receives metric samples from the k6 Engine as they're emitted.
+func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
+ for _, sample := range samples {
+ all := sample.GetSamples()
+ fmt.Fprintf(l.out, "%s [%s]\n", all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
+ }
+}
+
+// metricKeyValues returns a string of key-value pairs for all metrics in the sample.
+func metricKeyValues(samples []metrics.Sample) string {
+ names := make([]string, 0, len(samples))
+ for _, sample := range samples {
+ names = append(names, fmt.Sprintf("%s=%v", sample.Metric.Name, sample.Value))
+ }
+ return strings.Join(names, ", ")
+}
+
+// Stop finalizes any tasks in progress, closes network connections, etc.
+func (*Logger) Stop() error {
+ return nil
+}
+```
+
+{{< /code >}}
+
+Notice a couple of things:
+
+- The module initializer `New()` receives an instance of
+ [`output.Params`](https://pkg.go.dev/go.k6.io/k6/output#Params).
+ With this object, the extension can access the output-specific configuration,
+ interfaces to the filesystem, synchronized stdout and stderr, and more.
+
+- `AddMetricSamples` in this example writes to stdout. This output might have
+ to be buffered and flushed periodically in a real-world scenario to avoid memory
+ leaks. Below we'll discuss some helpers you can use for that.
+
+## Compile your extended k6
+
+To build a k6 binary with this extension, run:
+
+```bash
+$ xk6 build --with xk6-output-logger=.
+```
+
+{{% admonition type="note" %}}
+
+`xk6-output-logger` is the Go module name passed to `go mod init`
+
+Usually, this would be a URL similar to `github.com/grafana/xk6-output-logger`.
+
+{{% /admonition %}}
+
+## Use your extension
+
+Now we can use the extension with a test script.
+
+1. In new JavaScript file, make some simple test logic.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { sleep } from 'k6';
+
+export default function () {
+ http.get('https://test-api.k6.io');
+ sleep(0.5);
+}
+```
+
+{{< /code >}}
+
+1. Now, run the test.
+
+```bash
+$ ./k6 run test.js --out logger --quiet --no-summary --iterations 2
+```
+
+{{% admonition type="note" %}}
+
+The `--out logger` argument tells k6 to use your custom output. The flag
+`--quiet --no-summary` configures k6 to show only custom output.
+
+{{% /admonition %}}
+
+Your output should look something like this:
+
+```shell
+2022-07-01T08:55:09.59272-05:00 [http_reqs=1, http_req_duration=117.003, http_req_blocked=558.983, http_req_connecting=54.135, http_req_tls_handshaking=477.198, http_req_sending=0.102, http_req_waiting=116.544, http_req_receiving=0.357, http_req_failed=0]
+2022-07-01T08:55:09.917036-05:00 [vus=1, vus_max=1]
+2022-07-01T08:55:10.094196-05:00 [data_sent=446, data_received=21364, iteration_duration=1177.505083, iterations=1]
+2022-07-01T08:55:10.213926-05:00 [http_reqs=1, http_req_duration=119.122, http_req_blocked=0.015, http_req_connecting=0, http_req_tls_handshaking=0, http_req_sending=0.103, http_req_waiting=118.726, http_req_receiving=0.293, http_req_failed=0]
+2022-07-01T08:55:10.715323-05:00 [data_sent=102, data_received=15904, iteration_duration=620.862459, iterations=1]
+```
+
+## Things to keep in mind
+
+- Output structs can optionally implement additional interfaces that allow them to
+ receive [thresholds](https://pkg.go.dev/go.k6.io/k6/output#WithThresholds) or
+ [run-status updates](https://pkg.go.dev/go.k6.io/k6/output#WithRunStatusUpdates)
+ and even [interrupt a test](https://pkg.go.dev/go.k6.io/k6/output#WithTestRunStop).
+- Consider using [`output.SampleBuffer`](https://pkg.go.dev/go.k6.io/k6/output#SampleBuffer)
+ and [`output.PeriodicFlusher`](https://pkg.go.dev/go.k6.io/k6/output#PeriodicFlusher)
+ to improve performance given the large amounts of data produced by k6. Refer to
+ [`statsd` output](https://github.com/grafana/k6/blob/master/output/statsd/output.go) for an example.
+- Use the [project template](https://github.com/grafana/xk6-output-template) as a starting point
+ for your output extension.
+
+> Questions? Feel free to join the discussion on extensions in the [k6 Community Forum](https://community.grafana.com/c/grafana-k6/extensions/82).
diff --git a/docs/sources/v0.50.x/extensions/explanations/_index.md b/docs/sources/v0.50.x/extensions/explanations/_index.md
new file mode 100644
index 000000000..b8ac55466
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/explanations/_index.md
@@ -0,0 +1,10 @@
+---
+title: Explanations
+weight: 05
+---
+
+# Explanations
+
+
+
+{{< section >}}
diff --git a/docs/sources/v0.50.x/extensions/explanations/extension-graduation.md b/docs/sources/v0.50.x/extensions/explanations/extension-graduation.md
new file mode 100644
index 000000000..5f89bc8f9
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/explanations/extension-graduation.md
@@ -0,0 +1,56 @@
+---
+title: Extension Graduation
+description: Some extensions are created with the intent to become a part of core of k6.
+weight: 03
+---
+
+# Extension Graduation
+
+Some _Go_ extensions may one day be available within the k6 binary.
+These select extensions pass through different phases to become core functionality.
+
+This graduation process benefits both users and developers.
+k6 users can access new features and provide feedback to influence its developments.
+k6 developers meanwhile can iterate quickly and respond to feedback without worrying about breaking changes.
+
+A _core-bound_ extension passes through the following phases:
+![Extension graduation](/media/docs/k6-oss/extension-graduation.png)
+
+### Extension
+
+Most extensions in the k6 ecosystem remain _extensions_ requiring [xk6](https://github.com/grafana/xk6) to incorporate the custom functionality.
+These extensions might be provided by Grafana or by the community, and _may_ be included in the [Extensions Registry](https://grafana.com/docs/k6//extensions/explore).
+
+{{% admonition type="note" %}}
+
+Only Grafana-controlled extensions may progress beyond the _extension_ phase to become _experimental_ or _core modules_.
+
+{{% /admonition %}}
+
+### Experimental Module
+
+This phase is the first exposure to core k6.
+The extension is still maintained outside the core of k6 but imported as a Go module, no longer requiring xk6.
+
+Once an extension is promoted as an _experimental module_, the extension will be removed from the [extension listing](https://grafana.com/docs/k6//extensions/explore).
+At this time, documentation for the functionality will be provided in [k6 API](https://grafana.com/docs/k6//javascript-api/k6-experimental) and [output](https://grafana.com/docs/k6//results-output/real-time) for _JavaScript_ and _Output_ extensions, respectively.
+
+There should be a reasonably high degree of quality and stability at this point.
+This phase makes the feature accessible to more users, which in turn gives k6 developers more chances to receive feedback.
+The key will be to achieve a balance between usability and stability.
+
+{{% admonition type="caution" %}}
+
+Not all experimental modules will progress to become a core module!
+The k6 team reserves the right to discontinue and remove any experimental module if is no longer deemed desirable.
+
+{{% /admonition %}}
+
+### Core Module
+
+The stabilized feature is now part of the standard k6 product as a built-in module.
+An extension may be in the _experimental module_ phase for an extended time before progressing as a core module.
+
+The module code is in the main k6 repository, with the old extension repository archived.
+Options from the _experimental module_ phase are deprecated and removed after two k6 releases,
+providing time for users to upgrade scripts for the new module.
diff --git a/docs/sources/v0.50.x/extensions/explanations/extensions-registry.md b/docs/sources/v0.50.x/extensions/explanations/extensions-registry.md
new file mode 100644
index 000000000..dbcb075e7
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/explanations/extensions-registry.md
@@ -0,0 +1,103 @@
+---
+title: About the Extensions Registry
+description: Reasons for the registry and what is required to be included.
+weight: 01
+---
+
+# About the Extensions Registry
+
+Did you create an extension and want to share it with your fellow k6 users?
+We'd love to spread word of this new feature adding to our [registry](https://grafana.com/docs/k6//extensions/explore) of available extensions.
+However, before an extension is added to the registry, we must ensure that it is maintained to the registry standard.
+
+Our desire is to provide the best developer experience when using k6.
+This extends to the extensions ecosystem as well.
+The adaptability provided by k6 extensions opens a wide array of potential use cases.
+
+To ensure quality, we need a well-maintained, curated listing of extensions.
+Our pledge to the community is to make our best attempt to ensure the listed projects meet certain standards.
+While we cannot guarantee the quality of community-provided extensions, we _can_ aid the evaluation by requiring certain consistencies.
+
+## Registry Requirements
+
+At minimum, each source code repository must have the following:
+
+- **a README file**
+
+ The `README` must contain documentation such as the project description, build and usage instructions, as well as k6 version compatibility.
+ The goal is to provide enough information to quickly and easily evaluate the extension.
+
+- **the `xk6` topic set**
+
+ GitHub allows setting _topics_ for a repository.
+ This supports querying all public repositories based upon a keyword for better discoverability, i.e. ["xk6"](https://github.com/topics/xk6).
+ See the [GitHub documentation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics) to add topics.
+
+- **a non-restrictive license**
+
+ Any open source software (OSS) license will suffice, but [Apache2](https://www.apache.org/licenses/LICENSE-2.0) is preferred.
+
+- **an `examples` folder with examples**
+
+ Provide at least one script to show proper usage of your API.
+ If a [Docker Compose](https://docs.docker.com/compose/compose-file/) specification is provided, these could be used as integration tests to validate the extension works as intended.
+
+- **at least one versioned release**
+
+ As features or fixes are ready to be consumed, create a [release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository).
+ This promotes stability by allowing a user to utilize a particular version.
+ Use [semantic versioning](https://semver.org/) to communicate changes as the extension evolves.
+
+- **builds with a recent k6 version**
+
+ Ideally, the extension should build with the [latest release](https://github.com/grafana/k6/releases/latest).
+ But, it must build with a version of k6 that is no more than three releases old.
+ For example, if latest version of k6 is `v0.100`, the extension must build with at least version `v0.98`.
+ Be sure to also match the version of Go as determined by the version of k6.
+
+## Naming Conventions
+
+Some extensions may be very specific, where others are more general.
+Multiple extensions may even be created for the same product with different levels of support based upon version.
+By adhering to typical naming conventions, your extension name can remove some doubts as to what is supported.
+
+For any extension, we recommend the `xk6-` prefix as well as an optional `output-` for [Output extensions](https://grafana.com/docs/k6//extensions/create/output-extensions).
+Next, provide the product or protocol name; don't be cryptic.
+Ensure the usage is explicit by adopting only well-known acronyms or abbreviations if necessary.
+If your extension supports only a specific version of a product, incorporate the version into the name, for example `v2`.
+
+As an example, suppose an extension that outputs test metrics to the _AwesomeLog_ application, and it uses only the v2 API.
+In this case, say the latest v3 API is not backward-compatible.
+Applying our conventions, we'd recommend naming this repository as `xk6-output-awesomelog-v2`.
+
+{{% admonition type="note" %}}
+
+Our goal is to quickly understand the intent of the extension.
+
+{{% /admonition %}}
+
+## Extension Tiers
+
+Extensions come from multiple sources.
+To help distinguish extensions, we're now categorizing each extension into a _tier_.
+Each tier definition is as follows:
+
+- **Official Extensions**
+
+ _Official extensions_ are those owned and maintained by Grafana Labs.
+ They will have official documentation and have support provided by members of the Grafana organization.
+
+- **Community Extensions**
+
+ _Community extensions_ are created and maintained by an individual or group from the community at large.
+ These have no implied warranty or level of support.
+ The Grafana team will make best-effort assistance to keep popular projects in compliance.
+
+## Potential for De-listing
+
+Given our desire to provide the best developer experience when using k6, we reserve the right to de-list any extension we deem is no longer maintaining standards.
+Before any action takes place, the extension maintainers will be contacted to be given a chance to rectify the project and thus avoid de-listing.
+Such contact may be in the form of GitHub issues or merge requests.
+
+Should any extension be de-listed, this does not constitute a permanent removal.
+Any extension that has been de-listed may be reinstated once the reasons for the initial removal have been remediated.
diff --git a/docs/sources/v0.50.x/extensions/explanations/go-js-bridge.md b/docs/sources/v0.50.x/extensions/explanations/go-js-bridge.md
new file mode 100644
index 000000000..b80c8c625
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/explanations/go-js-bridge.md
@@ -0,0 +1,41 @@
+---
+title: About the Go-to-JS bridge
+description: Technical details about how JavaScript works in the goja engine.
+weight: 02
+---
+
+# About the Go-to-JS bridge
+
+All k6 and xk6 binaries have an embedded JavaScript engine, [goja](https://github.com/dop251/goja),
+which your test scripts run on.
+
+You will deepen your conceptual knowledge of how your k6 extension works if you understand the _bridge_ between Go internals and the JavaScript runtime.
+
+## Go-to-JavaScript bridge features
+
+The bridge has a few features we should highlight:
+
+- Go method names are converted from _Pascal_ to _Camel_ case when
+ accessed in JS. For example, `IsGreater` becomes `isGreater`.
+
+- Go field names convert from _Pascal_ to _Snake_ case. For example, the struct field `ComparisonResult string`
+ becomes `comparison_result` in JS.
+
+- Field names may be explicit using `js` struct tags. For example, declaring the field as ComparisonResult string `js:"result"`
+ or hiding from JS using `js:"-"`.
+
+## Type conversion and native constructors
+
+The JavaScript runtime transparently converts Go types like `int64` to their JS equivalent.
+For complex types where this is impossible, your script might fail with a `TypeError`, requiring you to explicitly convert
+your object to a [`goja.Object`](https://pkg.go.dev/github.com/dop251/goja#Object) or [`goja.Value`](https://pkg.go.dev/github.com/dop251/goja#Value).
+
+```go
+func (*Compare) XComparator(call goja.ConstructorCall, rt *goja.Runtime) *goja.Object {
+ return rt.ToValue(&Compare{}).ToObject(rt)
+}
+```
+
+The preceding snippet also demonstrates the _native constructors_ feature from goja, where methods can become JS constructors.
+Methods with this signature can create `Comparator` instances in JS with `new compare.Comparator()`.
+While this is more idiomatic to JS, it still has the benefit of receiving the `goja.Runtime`.
diff --git a/docs/sources/v0.50.x/extensions/explore.md b/docs/sources/v0.50.x/extensions/explore.md
new file mode 100644
index 000000000..63f1e26c4
--- /dev/null
+++ b/docs/sources/v0.50.x/extensions/explore.md
@@ -0,0 +1,348 @@
+---
+title: 'Explore k6 extensions'
+menuTitle: 'Explore extensions'
+weight: 01
+---
+
+# Explore k6 extensions
+
+With over 50 available extensions, the k6 extension ecosystem has many options to meet your requirements and help you incorporate new protocol access, embed a particular client, or improve your test performance. Extensions are developed both by the k6 developers and by the open-source developer community.
+
+Extensions are composable; you can combine any extensions, or mix and match different test cases. You can use [Go and xk6](https://grafana.com/docs/k6//extensions/build-k6-binary-using-go/) or [Docker](https://grafana.com/docs/k6//extensions/build-k6-binary-using-docker/) to build your custom k6 binary:
+
+{{< code >}}
+
+```go-and-xk6
+xk6 build \
+ --with github.com/grafana/xk6-sql@v0.0.1 \
+ --with github.com/grafana/xk6-output-prometheus-remote
+```
+
+```docker-in-linux
+docker run --rm -u "$(id -u):$(id -g)" -v "${PWD}:/xk6" grafana/xk6 build \
+ --with github.com/grafana/xk6-sql@v0.0.1 \
+ --with github.com/grafana/xk6-output-prometheus-remote
+```
+
+{{< /code >}}
+
+
+
+Use the table to explore the many extensions. Questions? Feel free to join the discussion about extensions in the [k6 Community Forum](https://community.grafana.com/c/grafana-k6/extensions/82).
+
+
+
+Don't see what you need? Learn how you can [create a custom extension](https://grafana.com/docs/k6//extensions/create/).
diff --git a/docs/sources/v0.50.x/get-started/_index.md b/docs/sources/v0.50.x/get-started/_index.md
new file mode 100644
index 000000000..6df58599b
--- /dev/null
+++ b/docs/sources/v0.50.x/get-started/_index.md
@@ -0,0 +1,10 @@
+---
+weight: 01
+title: Get started
+---
+
+# Get started
+
+
+
+{{< section >}}
diff --git a/docs/sources/v0.50.x/get-started/installation/_index.md b/docs/sources/v0.50.x/get-started/installation/_index.md
new file mode 100644
index 000000000..3d7493a47
--- /dev/null
+++ b/docs/sources/v0.50.x/get-started/installation/_index.md
@@ -0,0 +1,81 @@
+---
+title: 'Installation'
+description: 'k6 has packages for Linux, Mac, and Windows. As alternatives, you can also using a Docker container or a standalone binary.'
+weight: 02
+weight: 02
+---
+
+# Installation
+
+k6 has packages for Linux, Mac, and Windows. Alternatively, you can use a Docker container or a standalone binary.
+
+## Linux
+
+### Debian/Ubuntu
+
+```bash
+sudo gpg -k
+sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
+echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
+sudo apt-get update
+sudo apt-get install k6
+```
+
+### Fedora/CentOS
+
+Using `dnf` (or `yum` on older versions):
+
+```bash
+sudo dnf install https://dl.k6.io/rpm/repo.rpm
+sudo dnf install k6
+```
+
+## MacOS
+
+Using [Homebrew](https://brew.sh/):
+
+```bash
+brew install k6
+```
+
+## Windows
+
+If you use the [Chocolatey package manager](https://chocolatey.org/) you can install the unofficial k6 package with:
+
+```
+choco install k6
+```
+
+If you use the [Windows Package Manager](https://github.com/microsoft/winget-cli), install the official packages from the k6 manifests [(created by the community)](https://github.com/microsoft/winget-pkgs/tree/master/manifests/k/k6/k6):
+
+```
+winget install k6 --source winget
+```
+
+Alternatively, you can download and run [the latest official installer](https://dl.k6.io/msi/k6-latest-amd64.msi).
+
+## Docker
+
+```bash
+docker pull grafana/k6
+```
+
+We also have a separate image you can use with `chromium` installed to run k6 browser tests.
+
+```bash
+docker pull grafana/k6:master-with-browser
+```
+
+## Download the k6 binary
+
+Our [GitHub Releases page](https://github.com/grafana/k6/releases) has a standalone binary for all platforms. After downloading and extracting the archive for your platform, place the `k6` or `k6.exe` binary in your `PATH` to run `k6` from any location.
+
+## Using k6 extensions
+
+If you use one or more [k6 extensions](https://grafana.com/docs/k6//extensions), you need a k6 binary built with your desired extensions.
+Head to [Explore extension](https://grafana.com/docs/k6//extensions/explore) to get started.
+
+## Troubleshooting
+
+If installation fails, check the [list of common installation issues](https://grafana.com/docs/k6//get-started/installation/troubleshooting).
+If your problem is not listed and persists, reach out via the channel `#community-discussion` on our [official Slack](https://k6io.slack.com/), or report it on our [community forum](https://community.grafana.com/).
diff --git a/docs/sources/v0.50.x/get-started/installation/troubleshooting.md b/docs/sources/v0.50.x/get-started/installation/troubleshooting.md
new file mode 100644
index 000000000..a8c624081
--- /dev/null
+++ b/docs/sources/v0.50.x/get-started/installation/troubleshooting.md
@@ -0,0 +1,37 @@
+---
+title: 'Troubleshooting'
+description: 'Instructions to fix the most common installation issues.'
+weight: 01
+---
+
+# Troubleshooting
+
+## System lacks ca-certificates or gnupg2
+
+Some Linux distributions don't come bundled with the `ca-certificates` and `gnupg2` packages.
+If you use such a distribution, you'll need to install them with:
+
+```bash
+sudo apt-get update && sudo apt-get install -y ca-certificates gnupg2
+```
+
+This example is for Debian/Ubuntu and derivatives. Consult your distribution's documentation if you use another one.
+
+## Behind a firewall or proxy
+
+Some users have reported that they can't download the key from Ubuntu's keyserver.
+When they run the `gpg` command, their firewalls or proxies block their requests to download.
+If this issue affects you, you can try this alternative:
+
+```bash
+curl -s https://dl.k6.io/key.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/k6-archive-keyring.gpg
+```
+
+## Old rpm-based Linux distributions
+
+Distributions like Amazon Linux 2 and CentOS before version 8 don't support the PGP V4 signature we use.
+You'll need to disable the verification when you install k6:
+
+```bash
+sudo yum install --nogpgcheck k6
+```
diff --git a/docs/sources/v0.50.x/get-started/resources.md b/docs/sources/v0.50.x/get-started/resources.md
new file mode 100644
index 000000000..592cafdd2
--- /dev/null
+++ b/docs/sources/v0.50.x/get-started/resources.md
@@ -0,0 +1,45 @@
+---
+title: k6 resources
+description: 'An overview of the k6 resources beyond the k6 docs: videos, repositories, test servers, courses, and more'
+weight: 05
+---
+
+# k6 resources
+
+The docs aim to cover everything necessary to use the core k6 products in your daily operational work.
+But scripting and testing are skills that take time to learn.
+What's more, k6 is an extensible tool, already incorporated with many other functionalities and protocols.
+
+These resources help you write and run k6 tests in a safe environment and explore how to use k6 with other applications.
+
+## Learning
+
+- [Get started with k6 tutorial](https://grafana.com/docs/k6//examples/get-started-with-k6). The getting started tutorial provides some procedures for common real-life uses of k6 and does not require prior knowledge of k6 or JavaScript
+
+- [k6 Learn](https://github.com/grafana/k6-learn). A repository with a course and a ton of learning resources
+- [k6 YouTube channel](https://www.youtube.com/c/k6test/playlists). Office hours, specific playlists, and other interesting videos from the community.
+- [Awesome k6](https://github.com/grafana/awesome-k6). A list of awesome stuff about k6.
+- [Examples](https://github.com/grafana/k6/tree/master/examples). A directory full of example k6 scripts for different use cases.
+
+## Community
+
+- [The k6 community forum](https://community.grafana.com/). Get support from the k6 team and community.
+- [Get in touch](https://k6.io/community/#join-the-conversation). Slack, Meetup, Twitter, Stack Overflow, LinkedIn, and more.
+
+## Test servers
+
+If you need a place to learn k6 and test your scripts, you can use these playground/demo servers:
+
+- [pizza.grafana.fun](https://pizza.grafana.fun/). A simple demo webapp. [grafana/quickpizza](https://github.com/grafana/quickpizza)
+- [k6-http.grafana.fun](https://k6-http.grafana.fun). A simple HTTP Request & Response Service. [grafana/httpbin](https://github.com/grafana/httpbin)
+- [grpcbin.test.k6.io](https://grpcbin.test.k6.io/). A simple gRPC Request & Response Service. [grafana/k6-grpcbin](https://github.com/grafana/k6-grpcbin)
+
+Note that these are shared testing environments - please avoid high-load tests. Alternatively, you can deploy and host them on your infrastructure and run the examples in the repository.
+
+## k6 + your favorite tool
+
+- [Kubernetes Operator](https://k6.io/blog/running-distributed-tests-on-k8s/). Distribute test execution across a Kubernetes cluster.
+- [xk6 extensions](https://grafana.com/docs/k6//extensions). Custom k6 binaries to support the tool you need.
+- [The browser recorder](https://grafana.com/docs/k6//using-k6/test-authoring/create-tests-from-recordings/using-the-browser-recorder). Make test scripts from browser sessions.
+- [k6 TypeScript template](https://github.com/grafana/k6-template-typescript)
+- [Integrations](https://grafana.com/docs/k6//misc/integrations)
diff --git a/docs/sources/v0.50.x/get-started/results-output.md b/docs/sources/v0.50.x/get-started/results-output.md
new file mode 100644
index 000000000..c3239c49e
--- /dev/null
+++ b/docs/sources/v0.50.x/get-started/results-output.md
@@ -0,0 +1,105 @@
+---
+title: 'Results output'
+description: 'For basic tests, the top-level summary that k6 provides might be enough. For detailed analysis, you can stream all data your test outputs to an external source.'
+weight: 04
+---
+
+# Results output
+
+As k6 generates load for your test, it also makes _metrics_ that measure the performance of the system.
+Broadly, you can analyze metrics in two ways:
+
+- As summary statistics, in an _end-of-test_ summary report.
+- In granular detail, with measurements for every data point across test (and timestamps)
+
+You can customize almost every aspect of result output:
+
+- Create custom metrics
+- Configure new summary statistics and print them to any text format.
+- Stream the results to one or multiple services of your choice (for example, InfluxDB or Prometheus).
+
+## Metrics
+
+**Documentation**: [Using metrics](https://grafana.com/docs/k6//using-k6/metrics)
+
+k6 comes with built-in metrics about the test load and the system response.
+Key metrics include:
+
+- `http_req_duration`, the end-to-end time of all requests (that is, the total latency)
+- `http_req_failed`, the total number of failed requests
+- `iterations`, the total number of iterations
+
+## End-of-test summary
+
+**Documentation**: [End-of-test summary](https://grafana.com/docs/k6//results-output/end-of-test)
+
+By default, k6 prints summarized results to `stdout`.
+
+When you run a test, k6 outputs a plain-text logo, your test progress, and some test details.
+After the test finishes, k6 prints the full details and summary statistics of the test metrics.
+
+![k6 results - console/stdout output](/media/docs/k6-oss/k6-results-stdout.png)
+
+The end-of-test summary shows aggregated statistical values for your result metrics, including:
+
+- Median and average values
+- Minimum and maximum values
+- p90, p95, and p99 values
+
+You can configure the statistics to report with the [`--summary-trend-stats`](https://grafana.com/docs/k6//using-k6/k6-options/reference#summary-trend-stats) option.
+For example, this command displays only median, p95, and p99.9 values.
+
+```sh
+k6 run --iterations=100 --vus=10 \
+--summary-trend-stats="med,p(95),p(99.9)" script.js
+```
+
+### Custom reports with `handleSummary()`
+
+For completely customized end-of-summary reports, k6 provides the `handleSummary()` function.
+
+At the end of the test, k6 automatically creates an object with all aggregated statistics.
+The `handleSummary()` function can process this object into a custom report in any text format: JSON, HTML, XML, and whatever else.
+
+## Time series and external outputs
+
+**Documentation**: [Real-time metrics](https://grafana.com/docs/k6//results-output/real-time)
+
+The condensed end-of-test summary provides a top-level view of the test.
+For deeper analysis, you need to look at granular time-series data,
+which has metrics and timestamps for every point of the test.
+
+You can access time-series metrics in two ways:
+
+- Write them to a JSON or CSV file.
+- Stream them to an external service.
+
+In both cases, you can use the `--out` flag and declare your export format as the flag argument.
+If you want to send the metrics to multiple sources, you can use multiple flags with multiple arguments:
+
+```sh
+k6 run \
+--out json=test.json \
+--out influxdb=http://localhost:8086/k6
+```
+
+The available built-in outputs include:
+
+
+
+- [Amazon CloudWatch](https://grafana.com/docs/k6//results-output/real-time/amazon-cloudwatch)
+- [Cloud](https://grafana.com/docs/k6//results-output/real-time/cloud)
+- [CSV](https://grafana.com/docs/k6//results-output/real-time/csv)
+- [Datadog](https://grafana.com/docs/k6//results-output/real-time/datadog)
+- [Dynatrace](https://grafana.com/docs/k6//results-output/real-time/dynatrace)
+- [Elasticsearch](https://grafana.com/docs/k6//results-output/real-time/elasticsearch)
+- [Grafana Cloud Prometheus](https://grafana.com/docs/k6//results-output/real-time/grafana-cloud-prometheus)
+- [InfluxDB](https://grafana.com/docs/k6//results-output/real-time/influxdb)
+- [JSON](https://grafana.com/docs/k6//results-output/real-time/json)
+- [Netdata](https://grafana.com/docs/k6//results-output/real-time/netdata)
+- [New Relic](https://grafana.com/docs/k6//results-output/real-time/new-relic)
+- [Prometheus](https://grafana.com/docs/k6//results-output/real-time/prometheus-remote-write)
+- [TimescaleDB](https://grafana.com/docs/k6//results-output/real-time/timescaledb)
+- [StatsD](https://grafana.com/docs/k6//results-output/real-time/statsd)
+
+
diff --git a/docs/sources/v0.50.x/get-started/running-k6.md b/docs/sources/v0.50.x/get-started/running-k6.md
new file mode 100644
index 000000000..d4ecf94d4
--- /dev/null
+++ b/docs/sources/v0.50.x/get-started/running-k6.md
@@ -0,0 +1,237 @@
+---
+title: 'Running k6'
+description: 'Follow along to learn how to run a test, add virtual users, increase the test duration, and ramp the number of requests up and down as the test runs.'
+weight: 03
+---
+
+# Running k6
+
+Follow along to learn how to:
+
+1. Run a test.
+2. Add virtual users.
+3. Increase the test duration.
+4. Ramp the number of requests up and down as the test runs.
+
+With these example snippets, you'll run the test with your machine's resources.
+But, if you have a k6 Cloud account, you can also use the `k6 cloud` command to outsource the test to k6 servers.
+
+## Run local tests
+
+To run a simple local script:
+
+1. Create and initialize a new script by running the following command:
+
+ {{< code >}}
+
+ ```linux
+ $ k6 new
+ ```
+
+ ```docker
+ $ docker run --rm -i -v $PWD:/app -w /app grafana/k6 new
+ ```
+
+ ```windows
+ PS C:\> docker run --rm -i -v ${PWD}:/app -w /app grafana/k6 init
+ ```
+
+ {{< /code >}}
+
+ This command creates a new script file named `script.js` in the current directory.
+ You can also specify a different file name as an argument to the `k6 new` command, for example `k6 new my-test.js`.
+
+1. Run k6 with the following command:
+
+ {{< code >}}
+
+ ```linux
+ $ k6 run script.js
+ ```
+
+ ```docker
+ # When using the `k6` docker image, you can't just give the script name since
+ # the script file will not be available to the container as it runs. Instead
+ # you must tell k6 to read `stdin` by passing the file name as `-`. Then you
+ # pipe the actual file into the container with `<` or equivalent. This will
+ # cause the file to be redirected into the container and be read by k6.
+
+ $ docker run --rm -i grafana/k6 run - cat script.js | docker run --rm -i grafana/k6 run -
+ ```
+
+ {{< /code >}}
+
+## Add VUs
+
+Now run a load test with more than one virtual user and a longer duration:
+
+{{< code >}}
+
+```linux
+$ k6 run --vus 10 --duration 30s script.js
+```
+
+```docker
+$ docker run --rm -i grafana/k6 run --vus 10 --duration 30s - cat script.js | docker run --rm -i grafana/k6 run --vus 10 --duration 30s -
+```
+
+{{< /code >}}
+
+_Running a 30-second, 10-VU load test_
+
+{{% admonition type="note" %}}
+
+k6 runs multiple iterations in parallel with _virtual users_ (VUs).
+In general terms, more virtual users means more simulated traffic.
+
+VUs are essentially parallel `while(true)` loops.
+Scripts are written in JavaScript, as ES6 modules,
+so you can break larger tests into smaller pieces or make reusable pieces as you like.
+
+{{% /admonition %}}
+
+### The init context and the default function
+
+For a test to run, you need to have _init code_, which prepares the test, and _VU code,_ which makes requests.
+
+Code in the init context defines functions and configures the test options (like `duration`).
+
+Every test also has a `default` function,
+which defines the VU logic.
+
+```javascript
+// init
+
+export default function () {
+ // vu code: do things here...
+}
+```
+
+Init code runs first and is called only once per VU.
+The `default` code runs as many times or as long as is configured in the test options.
+
+To learn more about how k6 executes, read about the [Test lifecycle](https://grafana.com/docs/k6//using-k6/test-lifecycle).
+
+## Set options
+
+Instead of typing `--vus 10` and `--duration 30s` each time you run the script,
+you can set the options in your JavaScript file:
+
+```javascript
+import http from 'k6/http';
+import { sleep } from 'k6';
+export const options = {
+ vus: 10,
+ duration: '30s',
+};
+export default function () {
+ http.get('http://test.k6.io');
+ sleep(1);
+}
+```
+
+If you run the script without flags, k6 uses the options defined in the script:
+
+{{< code >}}
+
+```linux
+$ k6 run script.js
+```
+
+```docker
+$ docker run --rm -i grafana/k6 run - cat script.js | docker run --rm -i grafana/k6 run -
+```
+
+{{< /code >}}
+
+## Ramp VUs up and down in stages
+
+You can ramp the number of VUs up and down during the test.
+To configure ramping, use the `options.stages` property.
+
+{{< code >}}
+
+```javascript
+import http from 'k6/http';
+import { check, sleep } from 'k6';
+
+export const options = {
+ stages: [
+ { duration: '30s', target: 20 },
+ { duration: '1m30s', target: 10 },
+ { duration: '20s', target: 0 },
+ ],
+};
+
+export default function () {
+ const res = http.get('https://httpbin.test.k6.io/');
+ check(res, { 'status was 200': (r) => r.status == 200 });
+ sleep(1);
+}
+```
+
+{{< /code >}}
+
+For more granular ramp configuration, you can use [scenarios](https://grafana.com/docs/k6//using-k6/scenarios) and the `ramping-vus` executor.
+
+## Execution modes
+
+{{% admonition type="note" %}}
+
+Portability is a major design goal of k6.
+
+You can run the same test in different modes with minimal changes.
+
+{{% /admonition %}}
+
+k6 supports three execution modes to run a k6 test: local, distributed, and cloud.
+
+- **Local**: the test execution happens entirely on a single machine, container, or CI server.
+
+ ```bash
+ k6 run script.js
+ ```
+
+- **Distributed**: the test execution is [distributed across a Kubernetes cluster](https://k6.io/blog/running-distributed-tests-on-k8s/).
+
+ Save the following YAML as `k6-resource.yaml`:
+
+ ```yaml
+ ---
+ apiVersion: k6.io/v1alpha1
+ kind: K6
+ metadata:
+ name: k6-sample
+ spec:
+ parallelism: 4
+ script:
+ configMap:
+ name: 'k6-test'
+ file: 'script.js'
+ ```
+
+ Apply the resource with the following command:
+
+ ```bash
+ kubectl apply -f /path/to/k6-resource.yaml
+ ```
+
+- **Cloud**: the test runs on [Grafana Cloud k6](https://grafana.com/docs/grafana-cloud/k6/get-started/run-cloud-tests-from-the-cli/).
+
+ ```bash
+ k6 cloud script.js
+ ```
+
+ Additionally, cloud-based solutions can run cloud tests on your [own cloud infrastructure](https://grafana.com/docs/grafana-cloud/k6/author-run/private-load-zone-v2/), and accept the test results from a [local](https://grafana.com/docs/k6//results-output/real-time/cloud) or [distributed test](https://github.com/grafana/k6-operator#k6-cloud-output).
diff --git a/docs/sources/v0.50.x/javascript-api/_index.md b/docs/sources/v0.50.x/javascript-api/_index.md
new file mode 100644
index 000000000..9f1bf26d8
--- /dev/null
+++ b/docs/sources/v0.50.x/javascript-api/_index.md
@@ -0,0 +1,268 @@
+---
+weight: 08
+title: JavaScript API
+---
+
+# JavaScript API
+
+The list of k6 modules natively supported in your k6 scripts.
+
+## Init context
+
+Before the k6 starts the test logic, code in the _init context_ prepares the script.
+A few functions are available only in init context.
+For details about the runtime, refer to the [Test lifecycle](https://grafana.com/docs/k6//using-k6/test-lifecycle).
+
+| Function | Description |
+| ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
+| [open( filePath, [mode] )](https://grafana.com/docs/k6//javascript-api/init-context/open) | Opens a file and reads all the contents into memory. |
+
+## k6
+
+The k6 module contains k6-specific functionality.
+
+| Function | Description |
+| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| [check(val, sets, [tags])](https://grafana.com/docs/k6//javascript-api/k6/check) | Runs one or more checks on a value and generates a pass/fail result but does not throw errors or otherwise interrupt execution upon failure. |
+| [fail([err])](https://grafana.com/docs/k6//javascript-api/k6/fail) | Throws an error, failing and aborting the current VU script iteration immediately. |
+| [group(name, fn)](https://grafana.com/docs/k6//javascript-api/k6/group) | Runs code inside a group. Used to organize results in a test. |
+| [randomSeed(int)](https://grafana.com/docs/k6//javascript-api/k6/random-seed) | Set seed to get a reproducible pseudo-random number using `Math.random`. |
+| [sleep(t)](https://grafana.com/docs/k6//javascript-api/k6/sleep) | Suspends VU execution for the specified duration. |
+
+## k6/crypto
+
+The k6/crypto `module` provides common hashing functionality available in the GoLang [crypto](https://golang.org/pkg/crypto/) package.
+
+| Function | Description |
+| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| [createHash(algorithm)](https://grafana.com/docs/k6//javascript-api/k6-crypto/createhash) | Create a Hasher object, allowing the user to add data to hash multiple times, and extract hash digests along the way. |
+| [createHMAC(algorithm, secret)](https://grafana.com/docs/k6//javascript-api/k6-crypto/createhmac) | Create an HMAC hashing object, allowing the user to add data to hash multiple times, and extract hash digests along the way. |
+| [hmac(algorithm, secret, data, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/hmac) | Use HMAC to sign an input string. |
+| [md4(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/md4) | Use MD4 to hash an input string. |
+| [md5(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/md5) | Use MD5 to hash an input string. |
+| [randomBytes(int)](https://grafana.com/docs/k6//javascript-api/k6-crypto/randombytes) | Return an array with a number of cryptographically random bytes. |
+| [ripemd160(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/ripemd160) | Use RIPEMD-160 to hash an input string. |
+| [sha1(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/sha1) | Use SHA-1 to hash an input string. |
+| [sha256(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/sha256) | Use SHA-256 to hash an input string. |
+| [sha384(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/sha384) | Use SHA-384 to hash an input string. |
+| [sha512(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/sha512) | Use SHA-512 to hash an input string. |
+| [sha512_224(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/sha512_224) | Use SHA-512/224 to hash an input string. |
+| [sha512_256(input, outputEncoding)](https://grafana.com/docs/k6//javascript-api/k6-crypto/sha512_256) | Use SHA-512/256 to hash an input string. |
+
+| Class | Description |
+| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [Hasher](https://grafana.com/docs/k6//javascript-api/k6-crypto/hasher) | Object returned by [crypto.createHash()](https://grafana.com/docs/k6//javascript-api/k6-crypto/createhash). It allows adding more data to be hashed and to extract digests along the way. |
+
+## k6/data
+
+The data module provides helpers to work with data.
+
+| Class/Method | Description |
+| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
+| [SharedArray](https://grafana.com/docs/k6//javascript-api/k6-data/sharedarray) | read-only array like structure that shares memory between VUs |
+
+## k6/encoding
+
+The encoding module provides [base64](https://en.wikipedia.org/wiki/Base64)
+encoding/decoding as defined by [RFC4648](https://tools.ietf.org/html/rfc4648).
+
+| Function | Description |
+| ----------------------------------------------------------------------------------------------------------------------- | ----------------------- |
+| [b64decode(input, [encoding], [format])](http://grafana.com/docs/k6//javascript-api/k6-encoding/b64decode/) | Base64 decode a string. |
+| [b64encode(input, [encoding])](http://grafana.com/docs/k6//javascript-api/k6-encoding/b64encode/) | Base64 encode a string. |
+
+## k6/execution
+
+`k6/execution` provides the capability to get information about the current test execution state inside the test script. You can read in your script the execution state during the test execution and change your script logic based on the current state.
+
+The `k6/execution` module provides the test execution information with the following properties:
+
+- [instance](#instance)
+- [scenario](#scenario)
+- [test](#test)
+- [vu](#vu)
+
+#### instance
+
+The instance object provides information associated with the load generator instance. You can think of it as the current running k6 process, which will likely be a single process if you are running k6 on your local machine. When running a cloud/distributed test with multiple load generator instances, the values of the following properties can differ across instances.
+
+| Property | Type | Description |
+| ---------------------- | ------- | ------------------------------------------------------------------------- |
+| iterationsInterrupted | integer | The number of prematurely interrupted iterations in the current instance. |
+| iterationsCompleted | integer | The number of completed iterations in the current instance. |
+| vusActive | integer | The number of active VUs. |
+| vusInitialized | integer | The number of currently initialized VUs. |
+| currentTestRunDuration | float | The time passed from the start of the current test run in milliseconds. |
+
+#### scenario
+
+Meta information and execution details about the current running [scenario](https://grafana.com/docs/k6//using-k6/scenarios).
+
+| Property | Type | Description |
+| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| name | string | The assigned name of the running scenario. |
+| executor | string | The name of the running [Executor](https://grafana.com/docs/k6//using-k6/scenarios#executors) type. |
+| startTime | integer | The Unix timestamp in milliseconds when the scenario started. |
+| progress | float | Percentage in a 0 to 1 interval of the scenario progress. |
+| iterationInInstance | integer | The unique and zero-based sequential number of the current iteration in the scenario, across the current instance. |
+| iterationInTest | integer | The unique and zero-based sequential number of the current iteration in the scenario. It is unique in all k6 execution modes - in local, cloud and distributed/segmented test runs. However, while every instance will get non-overlapping index values in cloud/distributed tests, they might iterate over them at different speeds, so the values won't be sequential across them. |
+
+#### test
+
+Control the test execution.
+
+| Property | Type | Description |
+| --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| abort([String]) | function | It aborts the test run with the exit code `108`, and an optional string parameter can provide an error message. Aborting the test will not prevent the `teardown()` execution. |
+| options | Object | It returns an object with all the test options as properties. The options' values are consolidated following the [order of precedence](https://grafana.com/docs/k6//using-k6/k6-options/how-to#order-of-precedence) and derived if shortcuts have been used. It returns `null` for properties where the relative option hasn't been defined. |
+
+#### vu
+
+Meta information and execution details about the current vu.
+
+| Property | Type | Description |
+| ------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| iterationInInstance | integer | The identifier of the iteration in the current instance for this VU. This is only unique for current VU and this instance (if multiple instances). This keeps being aggregated if a given VU is reused between multiple scenarios. |
+| iterationInScenario | integer | The identifier of the iteration in the current scenario for this VU. This is only unique for current VU and scenario it is currently executing. |
+| idInInstance | integer | The identifier of the VU across the instance. Not unique across multiple instances. |
+| idInTest | integer | The globally unique (across the whole test run) identifier of the VU. |
+
+## k6/experimental
+
+| Modules | Description |
+| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
+| [browser](https://grafana.com/docs/k6//javascript-api/k6-experimental/browser) | Provides browser-level APIs to interact with browsers and collect frontend performance metrics as part of your k6 tests. |
+| [redis](https://grafana.com/docs/k6//javascript-api/k6-experimental/redis) | Functionality to interact with [Redis](https://redis.io/). |
+| [timers](https://grafana.com/docs/k6//javascript-api/k6-experimental/timers) | `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` |
+| [tracing](https://grafana.com/docs/k6//javascript-api/k6-experimental/tracing) | Support for instrumenting HTTP requests with tracing information. |
+| [webcrypto](https://grafana.com/docs/k6//javascript-api/k6-experimental/webcrypto) | Implements the [WebCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). |
+| [websockets](https://grafana.com/docs/k6//javascript-api/k6-experimental/websockets) | Implements the browser's [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). |
+| [grpc](https://grafana.com/docs/k6//javascript-api/k6-experimental/grpc) | Extends `k6/net/grpc` with the streaming capabilities. |
+| [fs](https://grafana.com/docs/k6//javascript-api/k6-experimental/fs) | Provides a memory-efficient way to handle file interactions within your test scripts. |
+
+## k6/html
+
+The k6/html module contains functionality for HTML parsing.
+
+| Function | Description |
+| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
+| [parseHTML(src)](https://grafana.com/docs/k6//javascript-api/k6-html/parsehtml) | Parse an HTML string and populate a [Selection](https://grafana.com/docs/k6//javascript-api/k6-html/selection) object. |
+
+| Class | Description |
+| -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
+| [Element](https://grafana.com/docs/k6//javascript-api/k6-html/element) | An HTML DOM element as returned by the [Selection](https://grafana.com/docs/k6//javascript-api/k6-html/selection) API. |
+| [Selection](https://grafana.com/docs/k6//javascript-api/k6-html/selection) | A jQuery-like API for accessing HTML DOM elements. |
+
+## k6/http
+
+The k6/http module contains functionality for performing HTTP transactions.
+
+| Function | Description |
+| ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
+| [batch( requests )](https://grafana.com/docs/k6//javascript-api/k6-http/batch) | Issue multiple HTTP requests in parallel (like e.g. browsers tend to do). |
+| [cookieJar()](https://grafana.com/docs/k6//javascript-api/k6-http/cookiejar) | Get active HTTP Cookie jar. |
+| [del( url, [body], [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/del) | Issue an HTTP DELETE request. |
+| [file( data, [filename], [contentType] )](https://grafana.com/docs/k6//javascript-api/k6-http/file) | Create a file object that is used for building multi-part requests. |
+| [get( url, [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/get) | Issue an HTTP GET request. |
+| [head( url, [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/head) | Issue an HTTP HEAD request. |
+| [options( url, [body], [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/options) | Issue an HTTP OPTIONS request. |
+| [patch( url, [body], [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/patch) | Issue an HTTP PATCH request. |
+| [post( url, [body], [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/post) | Issue an HTTP POST request. |
+| [put( url, [body], [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/put) | Issue an HTTP PUT request. |
+| [request( method, url, [body], [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/request) | Issue any type of HTTP request. |
+| [asyncRequest( method, url, [body], [params] )](https://grafana.com/docs/k6//javascript-api/k6-http/asyncrequest) | Issue any type of HTTP request asynchronously. |
+| [setResponseCallback(expectedStatuses)](https://grafana.com/docs/k6//javascript-api/k6-http/set-response-callback) | Sets a response callback to mark responses as expected. |
+| [url\`url\`](https://grafana.com/docs/k6//javascript-api/k6-http/url) | Creates a URL with a name tag. Read more on [URL Grouping](https://grafana.com/docs/k6//using-k6/http-requests#url-grouping). |
+| [expectedStatuses( statusCodes )](https://grafana.com/docs/k6//javascript-api/k6-http/expected-statuses) | Create a callback for setResponseCallback that checks status codes. |
+
+| Class | Description |
+| -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
+| [CookieJar](https://grafana.com/docs/k6//javascript-api/k6-http/cookiejar) | Used for storing cookies, set by the server and/or added by the client. |
+| [FileData](https://grafana.com/docs/k6//javascript-api/k6-http/filedata) | Used for wrapping data representing a file when doing multipart requests (file uploads). |
+| [Params](https://grafana.com/docs/k6//javascript-api/k6-http/params) | Used for setting various HTTP request-specific parameters such as headers, cookies, etc. |
+| [Response](https://grafana.com/docs/k6//javascript-api/k6-http/response) | Returned by the http.\* methods that generate HTTP requests. |
+
+## k6/metrics
+
+The metrics module provides functionality to [create custom metrics](https://grafana.com/docs/k6//using-k6/metrics/create-custom-metrics) of various types.
+All metrics (both the [built-in metrics](https://grafana.com/docs/k6//using-k6/metrics/reference) and the custom ones) have a type.
+
+You can optionally [tag](https://grafana.com/docs/k6//using-k6/tags-and-groups) all values added to a custom metric, which can be useful when analysing the test results.
+
+| Metric type | Description |
+| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
+| [Counter](https://grafana.com/docs/k6//javascript-api/k6-metrics/counter) | A metric that cumulatively sums added values. |
+| [Gauge](https://grafana.com/docs/k6//javascript-api/k6-metrics/gauge) | A metric that stores the min, max and last values added to it. |
+| [Rate](https://grafana.com/docs/k6//javascript-api/k6-metrics/rate) | A metric that tracks the percentage of added values that are non-zero. |
+| [Trend](https://grafana.com/docs/k6//javascript-api/k6-metrics/trend) | A metric that calculates statistics on the added values (min, max, average, and percentiles). |
+
+## k6/net/grpc
+
+The `k6/net/grpc` module provides a [gRPC](https://grpc.io/) client for Remote Procedure Calls (RPC) over HTTP/2.
+
+| Class/Method | Description |
+| --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [Client](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/client) | gRPC client used for making RPC calls to a gRPC Server. |
+| [Client.load(importPaths, ...protoFiles)](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/client/client-load) | Loads and parses the given protocol buffer definitions to be made available for RPC requests. |
+| [Client.connect(address [,params])](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/client/client-connect) | Connects to a given gRPC service. |
+| [Client.invoke(url, request [,params])](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/client/client-invoke) | Makes an unary RPC for the given service/method and returns a [Response](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/response). |
+| [Client.close()](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/client/client-close) | Close the connection to the gRPC service. |
+| [Params](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/params) | RPC Request specific options. |
+| [Response](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/response) | Returned by RPC requests. |
+| [Constants](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/constants) | Define constants to distinguish between [gRPC Response](https://grafana.com/docs/k6//javascript-api/k6-net-grpc/response) statuses. |
+
+## k6/ws
+
+The ws module provides a [WebSocket](https://en.wikipedia.org/wiki/WebSocket) client implementing the [WebSocket protocol](http://www.rfc-editor.org/rfc/rfc6455.txt).
+
+| Function | Description |
+| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [connect( url, params, callback )](https://grafana.com/docs/k6//javascript-api/k6-ws/connect) | Create a WebSocket connection, and provides a [Socket](https://grafana.com/docs/k6//javascript-api/k6-ws/socket) client to interact with the service. The method blocks the test finalization until the connection is closed. |
+
+| Class/Method | Description |
+| --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| [Params](https://grafana.com/docs/k6//javascript-api/k6-ws/params) | Used for setting various WebSocket connection parameters such as headers, cookie jar, compression, etc. |
+| [Socket](https://grafana.com/docs/k6//javascript-api/k6-ws/socket) | WebSocket client used to interact with a WS connection. |
+| [Socket.close()](https://grafana.com/docs/k6//javascript-api/k6-ws/socket/socket-close) | Close the WebSocket connection. |
+| [Socket.on(event, callback)](https://grafana.com/docs/k6//javascript-api/k6-ws/socket/socket-on) | Set up an event listener on the connection for any of the following events:
- open
- binaryMessage
- message
- ping
- pong
- close
- error. |
+| [Socket.ping()](https://grafana.com/docs/k6//javascript-api/k6-ws/socket/socket-ping) | Send a ping. |
+| [Socket.send(data)](https://grafana.com/docs/k6//javascript-api/k6-ws/socket/socket-send) | Send string data. |
+| [Socket.sendBinary(data)](https://grafana.com/docs/k6//javascript-api/k6-ws/socket/socket-sendbinary) | Send binary data. |
+| [Socket.setInterval(callback, interval)](https://grafana.com/docs/k6//javascript-api/k6-ws/socket/socket-setinterval) | Call a function repeatedly at certain intervals, while the connection is open. |
+| [Socket.setTimeout(callback, period)](https://grafana.com/docs/k6//javascript-api/k6-ws/socket/socket-settimeout) | Call a function with a delay, if the connection is open. |
+
+## Error codes
+
+The following specific error codes are currently defined:
+
+- 1000: A generic error that isn't any of the ones listed below.
+- 1010: A non-TCP network error - this is a place holder there is no error currently known to trigger it.
+- 1020: An invalid URL was specified.
+- 1050: The HTTP request has timed out.
+- 1100: A generic DNS error that isn't any of the ones listed below.
+- 1101: No IP for the provided host was found.
+- 1110: Blacklisted IP was resolved or a connection to such was tried to be established.
+- 1111: Blacklisted hostname using The [Block Hostnames](https://grafana.com/docs/k6//using-k6/k6-options/reference#block-hostnames) option.
+- 1200: A generic TCP error that isn't any of the ones listed below.
+- 1201: A "broken pipe" on write - the other side has likely closed the connection.
+- 1202: An unknown TCP error - We got an error that we don't recognize but it is from the operating system and has `errno` set on it. The message in `error` includes the operation(write,read) and the errno, the OS, and the original message of the error.
+- 1210: General TCP dial error.
+- 1211: Dial timeout error - the timeout for the dial was reached.
+- 1212: Dial connection refused - the connection was refused by the other party on dial.
+- 1213: Dial unknown error.
+- 1220: Reset by peer - the connection was reset by the other party, most likely a server.
+- 1300: General TLS error
+- 1310: Unknown authority - the certificate issuer is unknown.
+- 1311: The certificate doesn't match the hostname.
+- 1400 to 1499: error codes that correspond to the [HTTP 4xx status codes for client errors](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors)
+- 1500 to 1599: error codes that correspond to the [HTTP 5xx status codes for server errors](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors)
+- 1600: A generic HTTP/2 error that isn't any of the ones listed below.
+- 1610: A general HTTP/2 GoAway error.
+- 1611 to 1629: HTTP/2 GoAway errors with the value of the specific [HTTP/2 error code](https://tools.ietf.org/html/rfc7540#section-7) added to 1611.
+- 1630: A general HTTP/2 stream error.
+- 1631 to 1649: HTTP/2 stream errors with the value of the specific [HTTP/2 error code](https://tools.ietf.org/html/rfc7540#section-7) added to 1631.
+- 1650: A general HTTP/2 connection error.
+- 1651 to 1669: HTTP/2 connection errors with the value of the specific [HTTP/2 error code](https://tools.ietf.org/html/rfc7540#section-7) added to 1651.
+- 1701: Decompression error.
+
+Read more about [Error codes](https://grafana.com/docs/k6//javascript-api/error-codes).
diff --git a/docs/sources/v0.50.x/javascript-api/error-codes.md b/docs/sources/v0.50.x/javascript-api/error-codes.md
new file mode 100644
index 000000000..88100b0c9
--- /dev/null
+++ b/docs/sources/v0.50.x/javascript-api/error-codes.md
@@ -0,0 +1,53 @@
+---
+title: 'Error Codes'
+description: 'Error codes are unique numbers that can be used to identify and handle different application and network errors more easily.'
+weight: 13
+---
+
+# Error Codes
+
+Error codes are unique numbers that can be used to identify and handle different application and network errors more easily. For the moment, these error codes are applicable only for errors that happen during HTTP requests, but they will be reused and extended to support other protocols in future k6 releases.
+
+When an error occurs, its code is determined and returned as both the `error_code` field of the [`http.Response`](https://grafana.com/docs/k6/