diff --git a/CONTRIBUTING/README.md b/CONTRIBUTING/README.md index 8474c38d65..b37bd662d4 100644 --- a/CONTRIBUTING/README.md +++ b/CONTRIBUTING/README.md @@ -212,6 +212,10 @@ The `nofail` option will allow the k6 code snippet to freely log errors without Any option taking the form of `env.KEY=VALUE` will be parsed by the `md-k6.py` script, and the corresponding `KEY=VALUE` pairing will be added to the environment variables when executing the k6 code snippet. Note that for `KEY` and `VALUE` the following characters are **not** allowed: `,`, `-`, and `$`. +### `nothresholds` Option + +The `nothresholds` options disables the processing of thresholds when running the code snippet (`--no-thresholds`). + ### `fixedscenarios` Option By default, all code snippets are run with whatever scenarios they define via their `options` variable. However, some command line arguments to `md-k6.py` can change this, for example: `-d`/`--duration`. This option replaces the scenarios for all code snippets run with a scenario lasting only a specific amount of time. However, this behavior may break some scripts, for this reason, it is possible to specify the `fixedscenarios` option to ensure that the code snippet scenarios will be used as they appear in the Markdown file. diff --git a/docs/sources/k6/next/examples/api-crud-operations.md b/docs/sources/k6/next/examples/api-crud-operations.md index 2339ad5291..e19fc88f68 100644 --- a/docs/sources/k6/next/examples/api-crud-operations.md +++ b/docs/sources/k6/next/examples/api-crud-operations.md @@ -19,14 +19,14 @@ This document has two examples, one that uses the core k6 APIs (`k6/http` and `c ## 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. +In the [setup() stage](https://grafana.com/docs/k6//using-k6/test-lifecycle#setup-and-teardown-stages) we create a user for [QuickPizza](https://quickpizza.grafana.com). 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. +1. _Create_ a new resource, a pizza rating. +2. _Read_ the list of ratings. +3. _Update_ the rating's stars (e.g. to 5 stars) and _read_ the rating to confirm the update operation. +4. _Delete_ the rating resource. ## Core k6 APIs example @@ -50,33 +50,37 @@ function randomString(length, charset = '') { } const USERNAME = `${randomString(10)}@example.com`; // Set your own email or `${randomString(10)}@example.com`; -const PASSWORD = 'superCroc2019'; +const PASSWORD = 'secret'; -const BASE_URL = 'https://test-api.k6.io'; +const BASE_URL = 'https://quickpizza.grafana.com'; // 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, - }); + const res = http.post( + `${BASE_URL}/api/users`, + JSON.stringify({ + 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 loginRes = http.post( + `${BASE_URL}/api/users/token/login`, + JSON.stringify({ + username: USERNAME, + password: PASSWORD, + }) + ); - const authToken = loginRes.json('access'); - check(authToken, { 'logged in successfully': () => authToken !== '' }); + const authToken = loginRes.json('token'); + check(authToken, { 'logged in successfully': () => authToken.length > 0 }); return authToken; } -export default (authToken) => { +export default function (authToken) { // set the authorization header on the session for the subsequent requests const requestConfigWithTag = (tag) => ({ headers: { @@ -85,64 +89,63 @@ export default (authToken) => { tags: Object.assign( {}, { - name: 'PrivateCrocs', + name: 'PrivateRatings', }, tag ), }); - let URL = `${BASE_URL}/my/crocodiles/`; + let URL = `${BASE_URL}/api/ratings`; - group('01. Create a new crocodile', () => { + group('01. Create a new rating', () => { const payload = { - name: `Name ${randomString(10)}`, - sex: 'F', - date_of_birth: '2023-05-11', + stars: 2, + pizza_id: 1, // Pizza ID 1 already exists in the database. }; - const res = http.post(URL, payload, requestConfigWithTag({ name: 'Create' })); + const res = http.post(URL, JSON.stringify(payload), requestConfigWithTag({ name: 'Create' })); - if (check(res, { 'Croc created correctly': (r) => r.status === 201 })) { - URL = `${URL}${res.json('id')}/`; + if (check(res, { 'Rating created correctly': (r) => r.status === 201 })) { + URL = `${URL}/${res.json('id')}`; } else { - console.log(`Unable to create a Croc ${res.status} ${res.body}`); + console.log(`Unable to create rating ${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('02. Fetch my ratings', () => { + const res = http.get(`${BASE_URL}/api/ratings`, requestConfigWithTag({ name: 'Fetch' })); + check(res, { 'retrieve ratings status': (r) => r.status === 200 }); + check(res.json(), { 'retrieved ratings list': (r) => r.ratings.length > 0 }); }); - group('03. Update the croc', () => { - const payload = { name: 'New name' }; - const res = http.patch(URL, payload, requestConfigWithTag({ name: 'Update' })); + group('03. Update the rating', () => { + const payload = { stars: 5 }; + const res = http.put(URL, JSON.stringify(payload), requestConfigWithTag({ name: 'Update' })); const isSuccessfulUpdate = check(res, { 'Update worked': () => res.status === 200, - 'Updated name is correct': () => res.json('name') === 'New name', + 'Updated stars number is correct': () => res.json('stars') === 5, }); if (!isSuccessfulUpdate) { - console.log(`Unable to update the croc ${res.status} ${res.body}`); + console.log(`Unable to update the rating ${res.status} ${res.body}`); return; } }); - group('04. Delete the croc', () => { + group('04. Delete the rating', () => { const delRes = http.del(URL, null, requestConfigWithTag({ name: 'Delete' })); const isSuccessfulDelete = check(null, { - 'Croc was deleted correctly': () => delRes.status === 204, + 'Rating was deleted correctly': () => delRes.status === 204, }); if (!isSuccessfulDelete) { - console.log(`Croc was not deleted properly`); + console.log('Rating was not deleted properly'); return; } }); -}; +} ``` {{< /code >}} @@ -167,35 +170,39 @@ export const options = { }; const USERNAME = `user${randomIntBetween(1, 100000)}@example.com`; // Set your own email; -const PASSWORD = 'superCroc2019'; +const PASSWORD = 'secret'; -const session = new Httpx({ baseURL: 'https://test-api.k6.io' }); +const session = new Httpx({ baseURL: 'https://quickpizza.grafana.com' }); // 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, - }); + const resp = session.post( + `/api/users`, + JSON.stringify({ + 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, - }); + const resp = session.post( + `/api/users/token/login`, + JSON.stringify({ + 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'); + authToken = resp.json('token'); expect(authToken, 'Authentication token').to.be.a('string'); }); @@ -206,54 +213,53 @@ 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) => { + describe('01. Create a new rating', (t) => { const payload = { - name: `Croc name ${randomString(10)}`, - sex: randomItem(['M', 'F']), - date_of_birth: '2023-05-11', + stars: 2, + pizza_id: 1, // Pizza ID 1 already exists in the database }; session.addTag('name', 'Create'); - const resp = session.post(`/my/crocodiles/`, payload); + const resp = session.post(`/api/ratings`, JSON.stringify(payload)); - expect(resp.status, 'Croc creation status').to.equal(201); - expect(resp, 'Croc creation valid json response').to.have.validJsonBody(); + expect(resp.status, 'Rating creation status').to.equal(201); + expect(resp, 'Rating creation valid json response').to.have.validJsonBody(); - session.newCrocId = resp.json('id'); + session.newRatingId = resp.json('id'); }); - describe('02. Fetch private crocs', (t) => { + describe('02. Fetch my ratings', (t) => { session.clearTag('name'); - const resp = session.get('/my/crocodiles/'); + const resp = session.get('/api/ratings'); - 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); + expect(resp.status, 'Fetch ratings status').to.equal(200); + expect(resp, 'Fetch ratings valid json response').to.have.validJsonBody(); + expect(resp.json('ratings').length, 'Number of ratings').to.be.above(0); }); - describe('03. Update the croc', (t) => { + describe('03. Update the rating', (t) => { const payload = { - name: `New croc name ${randomString(10)}`, + stars: 5, }; - const resp = session.patch(`/my/crocodiles/${session.newCrocId}/`, payload); + const resp = session.patch(`/api/ratings/${session.newRatingId}`, JSON.stringify(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); + expect(resp.status, 'Rating patch status').to.equal(200); + expect(resp, 'Fetch rating valid json response').to.have.validJsonBody(); + expect(resp.json('stars'), 'Stars').to.equal(payload.stars); - // read "croc" again to verify the update worked - const resp1 = session.get(`/my/crocodiles/${session.newCrocId}/`); + // read rating again to verify the update worked + const resp1 = session.get(`/api/ratings/${session.newRatingId}`); - 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); + expect(resp1.status, 'Fetch rating status').to.equal(200); + expect(resp1, 'Fetch rating valid json response').to.have.validJsonBody(); + expect(resp1.json('stars'), 'Stars').to.equal(payload.stars); }); - describe('04. Delete the croc', (t) => { - const resp = session.delete(`/my/crocodiles/${session.newCrocId}/`); + describe('04. Delete the rating', (t) => { + const resp = session.delete(`/api/ratings/${session.newRatingId}`); - expect(resp.status, 'Croc delete status').to.equal(204); + expect(resp.status, 'Rating delete status').to.equal(204); }); } ``` diff --git a/docs/sources/k6/next/examples/functional-testing.md b/docs/sources/k6/next/examples/functional-testing.md index eb661827cc..c19317a99b 100644 --- a/docs/sources/k6/next/examples/functional-testing.md +++ b/docs/sources/k6/next/examples/functional-testing.md @@ -25,11 +25,13 @@ export const options = { export default function () { describe('Hello world!', () => { - const response = http.get('https://test-api.k6.io/public/crocodiles/'); + const response = http.get('https://quickpizza.grafana.com/api/ratings', { + headers: { Authorization: 'Token abcdef0123456789' }, + }); expect(response.status, 'response status').to.equal(200); expect(response).to.have.validJsonBody(); - expect(response.json(), 'croc list').to.be.an('array'); + expect(response.json('ratings'), 'ratings list').to.be.an('array'); }); } ``` @@ -42,6 +44,7 @@ This test goes through several steps. It creates a new user account, authenticat {{< code >}} + ```javascript @@ -61,14 +64,17 @@ export let options = { iterations: 1, }; -let session = new Httpx({ baseURL: 'https://test-api.k6.io' }); +let session = new Httpx({ + baseURL: 'https://quickpizza.grafana.com', + headers: { Authorization: 'Token abcdef0123456789' }, +}); -function retrieveIndividualCrocodilesInABatch() { - describe('[Crocs service] Fetch public crocs one by one', () => { +function retrieveIndividualRatingsInABatch() { + describe('[Ratings service] Fetch public ratings one by one', () => { let responses = session.batch([ - new Get('/public/crocodiles/1/'), - new Get('/public/crocodiles/2/'), - new Get('/public/crocodiles/3/'), + new Get('/api/ratings/1'), + new Get('/api/ratings/2'), + new Get('/api/ratings/3'), ]); expect(responses, 'responses').to.be.an('array'); @@ -76,20 +82,20 @@ function retrieveIndividualCrocodilesInABatch() { 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'); + expect(response.json(), 'rating').to.be.an('object'); + expect(response.json(), 'rating').to.include.keys('id', 'stars', 'pizza_id'); + expect(response.json(), 'rating').to.not.have.a.property('language'); }); }); } -function retrieveAllPublicCrocodiles() { +function retrieveAllPublicRatings() { describe('[Crocs service] Fetch a list of crocs', () => { - let response = session.get('/public/crocodiles'); + let response = session.get('/api/ratings'); 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); + expect(response.json('ratings'), 'ratings list').to.be.an('array').lengthOf.above(2); }); } @@ -101,12 +107,9 @@ function validateAuthService() { let sampleUser = { username: USERNAME, password: PASSWORD, - email: USERNAME, - first_name: 'John', - last_name: 'Smith', }; - let response = session.post(`/user/register/`, sampleUser); + let response = session.post(`/api/users`, JSON.stringify(sampleUser)); expect(response.status, 'registration status').to.equal(201); expect(response).to.have.validJsonBody(); @@ -118,51 +121,50 @@ function validateAuthService() { password: PASSWORD, }; - let resp = session.post(`/auth/token/login/`, authData); + let resp = session.post(`/api/users/token/login`, JSON.stringify(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'); + expect(resp.json()).to.have.a.property('token'); + expect(resp.json('token'), 'auth token').to.be.a('string'); - let authToken = resp.json('access'); + let authToken = resp.json('token'); // set the authorization header on the session for the subsequent requests. - session.addHeader('Authorization', `Bearer ${authToken}`); + session.addHeader('Authorization', `Token ${authToken}`); }); } -function validateCrocodileCreation() { +function validateRatingCreation() { // authentication happened before this call. - describe('[Croc service] Create a new crocodile', () => { + describe('[Ratings service] Create a new rating', () => { let payload = { - name: `Croc Name`, - sex: 'M', - date_of_birth: '2019-01-01', + stars: 2, + pizza_id: 1, }; - let resp = session.post(`/my/crocodiles/`, payload); + let resp = session.post(`/api/ratings`, JSON.stringify(payload)); - expect(resp.status, 'Croc creation status').to.equal(201); + expect(resp.status, 'Rating creation status').to.equal(201); expect(resp).to.have.validJsonBody(); - session.newCrocId = resp.json('id'); // caching croc ID for the future. + session.newRatingId = resp.json('id'); // caching ID for the future }); - describe('[Croc service] Fetch private crocs', () => { - let response = session.get('/my/crocodiles/'); + describe('[Ratings service] Fetch private ratings', () => { + let response = session.get('/api/ratings'); expect(response.status, 'response status').to.equal(200); - expect(response, 'private crocs').to.have.validJsonBody(); - expect(response.json(), 'private crocs').to.not.be.empty; + expect(response, 'ratings').to.have.validJsonBody(); + expect(response.json('ratings'), 'ratings').to.not.be.empty; }); } export default function testSuite() { - retrieveIndividualCrocodilesInABatch(); - retrieveAllPublicCrocodiles(); + retrieveIndividualRatingsInABatch(); + retrieveAllPublicRatings(); validateAuthService(); - validateCrocodileCreation(); + validateRatingCreation(); } ``` @@ -174,10 +176,11 @@ Here's an auto-generated k6 test script showcasing all examples from the [Chaijs {{< code >}} + ```javascript -import { describe, expect, chai } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js'; +import chai, { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.5.0.1/index.js'; chai.config.aggregateChecks = false; chai.config.logFailures = true; diff --git a/docs/sources/k6/next/examples/get-started-with-k6/analyze-results.md b/docs/sources/k6/next/examples/get-started-with-k6/analyze-results.md index f447af313d..a9be2736f1 100644 --- a/docs/sources/k6/next/examples/get-started-with-k6/analyze-results.md +++ b/docs/sources/k6/next/examples/get-started-with-k6/analyze-results.md @@ -94,6 +94,8 @@ jq '. | select(.type == "Point" and .metric == "http_req_duration") | .data.valu 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: { @@ -113,10 +115,10 @@ Create a new script named "tagged-login.js", and add a custom tag to it. import http from 'k6/http'; export default function () { - const url = 'https://test-api.k6.io'; + const url = 'https://quickpizza.grafana.com'; const payload = JSON.stringify({ username: 'test_case', - password: '1234', + password: '12345678', }); const params = { @@ -129,8 +131,8 @@ export default function () { }, }; - //Login with tags - http.post(`${url}/auth/basic/login`, payload, params); + //Create user, with tags + http.post(`${url}/api/users`, payload, params); } ``` diff --git a/docs/sources/k6/next/examples/get-started-with-k6/test-for-functional-behavior.md b/docs/sources/k6/next/examples/get-started-with-k6/test-for-functional-behavior.md index 4fe0ae22b9..80cf90e2bb 100644 --- a/docs/sources/k6/next/examples/get-started-with-k6/test-for-functional-behavior.md +++ b/docs/sources/k6/next/examples/get-started-with-k6/test-for-functional-behavior.md @@ -30,10 +30,10 @@ import http from 'k6/http'; export default function () { // define URL and payload - const url = 'https://test-api.k6.io/auth/basic/login/'; + const url = 'https://quickpizza.grafana.com/api/users/token/login'; const payload = JSON.stringify({ - username: 'test_case', - password: '1234', + username: 'default', + password: '12345678', }); const params = { @@ -74,6 +74,7 @@ As an optional step, you can log the response body to the console to make sure y {{< code >}} + ```javascript @@ -105,11 +106,11 @@ 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/'; + // define URL and payload + const url = 'https://quickpizza.grafana.com/api/users/token/login'; const payload = JSON.stringify({ - username: 'test_case', - password: '1234', + username: 'default', + password: '12345678', }); const params = { headers: { diff --git a/docs/sources/k6/next/examples/get-started-with-k6/test-for-performance.md b/docs/sources/k6/next/examples/get-started-with-k6/test-for-performance.md index acc56aa802..bbb24c6d53 100644 --- a/docs/sources/k6/next/examples/get-started-with-k6/test-for-performance.md +++ b/docs/sources/k6/next/examples/get-started-with-k6/test-for-performance.md @@ -32,6 +32,8 @@ To codify the SLOs, add [_thresholds_](https://grafana.com/docs/k6// Thresholds are set in the [`options`](https://grafana.com/docs/k6//using-k6/k6-options) object. + + ```javascript export const options = { // define thresholds @@ -62,9 +64,9 @@ export const options = { export default function () { // define URL and request body - const url = 'https://test-api.k6.io/auth/basic/login/'; + const url = 'https://quickpizza.grafana.com/api/users/token/login'; const payload = JSON.stringify({ - username: 'test_case', + username: 'default', password: '1234', }); const params = { @@ -130,6 +132,8 @@ 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 @@ -184,6 +188,8 @@ To do this: 1. Add the `abortOnFail` property to `http_req_failed`. + + ```javascript export const options = { // define thresholds @@ -197,6 +203,8 @@ export const options = { 1. Update the `scenarios` property to ramp the test up until it fails. + + ```javascript export const options = { thresholds: { @@ -227,6 +235,8 @@ Here is the full script. {{< code >}} + + ```javascript // import necessary modules import { check } from 'k6'; @@ -260,9 +270,9 @@ export const options = { export default function () { // define URL and request body - const url = 'https://test-api.k6.io/auth/basic/login/'; + const url = 'https://quickpizza.grafana.com/api/users/token/login'; const payload = JSON.stringify({ - username: 'test_case', + username: 'default', password: '1234', }); const params = { diff --git a/docs/sources/k6/next/examples/http2.md b/docs/sources/k6/next/examples/http2.md index cf45edb811..c953ab8932 100644 --- a/docs/sources/k6/next/examples/http2.md +++ b/docs/sources/k6/next/examples/http2.md @@ -17,7 +17,7 @@ import http from 'k6/http'; import { check } from 'k6'; export default function () { - const res = http.get('https://test-api.k6.io/'); + const res = http.get('https://quickpizza.grafana.com'); check(res, { 'status is 200': (r) => r.status === 200, 'protocol is HTTP/2': (r) => r.proto === 'HTTP/2.0', diff --git a/docs/sources/k6/next/examples/single-request.md b/docs/sources/k6/next/examples/single-request.md index eed8747015..231da655c5 100644 --- a/docs/sources/k6/next/examples/single-request.md +++ b/docs/sources/k6/next/examples/single-request.md @@ -17,7 +17,7 @@ export const options = { }; export default function () { - const response = http.get('https://test-api.k6.io/public/crocodiles/'); + const response = http.get('https://quickpizza.grafana.com'); } ``` diff --git a/docs/sources/k6/next/examples/websockets.md b/docs/sources/k6/next/examples/websockets.md index 6db2b362dd..6fca818fc4 100644 --- a/docs/sources/k6/next/examples/websockets.md +++ b/docs/sources/k6/next/examples/websockets.md @@ -7,21 +7,22 @@ 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/). +Here's a load test for the QuickPizza WebSocket API, available on https://quickpizza.grafana.com/ws. -Multiple VUs join a chat room and discuss various things for up to 1 minute, after which they disconnect. +Multiple VUs connect using a VU-indexed user name, and send random messages. -Each VU receives messages sent by all chat participants. +Each VU receives messages sent by all other VUs. {{< 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 +const sessionDuration = randomIntBetween(3000, 6000); // user session between 3s and 6s export const options = { vus: 10, @@ -29,18 +30,25 @@ export const options = { }; export default function () { - const url = `wss://test-api.k6.io/ws/crocochat/${chatRoomName}/`; + const url = `wss://quickpizza.grafana.com/ws`; const params = { tags: { my_tag: 'my ws session' } }; + const user = `user_${__VU}`; 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.send(JSON.stringify({ msg: 'Hello!', user: user })); 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.send( + JSON.stringify({ + user: user, + msg: `I'm saying ${randomString(5)}`, + foo: 'bar', + }) + ); + }, randomIntBetween(1000, 2000)); // say something every 1-2 seconds }); socket.on('ping', function () { @@ -56,19 +64,13 @@ export default function () { }); 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}`); - } + const data = JSON.parse(message); + console.log(`VU ${__VU} received message: ${data.msg}`); }); socket.setTimeout(function () { - console.log(`VU ${__VU}: ${sessionDuration}ms passed, leaving the chat`); - socket.send(JSON.stringify({ event: 'LEAVE' })); + console.log(`VU ${__VU}: ${sessionDuration}ms passed, leaving the website`); + socket.send(JSON.stringify({ msg: 'Goodbye!', user: user })); }, sessionDuration); socket.setTimeout(function () { @@ -78,6 +80,7 @@ export default function () { }); check(res, { 'Connected successfully': (r) => r && r.status === 101 }); + sleep(1); } ``` diff --git a/docs/sources/k6/next/extensions/create/output-extensions.md b/docs/sources/k6/next/extensions/create/output-extensions.md index a7fbcf0f75..2774898fb4 100644 --- a/docs/sources/k6/next/extensions/create/output-extensions.md +++ b/docs/sources/k6/next/extensions/create/output-extensions.md @@ -202,7 +202,7 @@ import http from 'k6/http'; import { sleep } from 'k6'; export default function () { - http.get('https://test-api.k6.io'); + http.get('https://quickpizza.grafana.com'); sleep(0.5); } ``` diff --git a/docs/sources/k6/next/get-started/resources.md b/docs/sources/k6/next/get-started/resources.md index ca383da5cc..412022e830 100644 --- a/docs/sources/k6/next/get-started/resources.md +++ b/docs/sources/k6/next/get-started/resources.md @@ -30,7 +30,6 @@ These resources help you write and run k6 tests in a safe environment and explor If you need a place to learn k6 and test your scripts, you can use these playground/demo applications: -- [test-api.k6.io](https://test-api.k6.io/). A simple REST and WebSocket web application. [grafana/test-api.k6.io](https://github.com/grafana/test-api.k6.io) - [grafana/quickpizza](https://github.com/grafana/quickpizza). A simple demo web application. 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. diff --git a/docs/sources/k6/next/get-started/write-your-first-test.md b/docs/sources/k6/next/get-started/write-your-first-test.md index bc03c4a63f..3cfeedfd01 100644 --- a/docs/sources/k6/next/get-started/write-your-first-test.md +++ b/docs/sources/k6/next/get-started/write-your-first-test.md @@ -45,6 +45,8 @@ Let’s walk through creating a simple test which performs 10 `GET` HTTP request 2. **Import k6 modules**: As the end goal here is to perform HTTP requests, import the k6 `http` module at the top of the file. To help simulate a real-world scenario, import the `sleep` function from the `k6` module as well. + + ```javascript // Import the http module to make HTTP requests. From this point, you can use `http` methods to make HTTP requests. import http from 'k6/http'; @@ -55,6 +57,8 @@ Let’s walk through creating a simple test which performs 10 `GET` HTTP request 3. **Define options**: To perform 10 HTTP requests, define an options block to configure the test execution. In this case, set the number of iterations to 10 to instruct k6 to execute the default function 10 times. Right beneath the imports, add the following code: + + ```javascript import http from 'k6/http'; import { sleep } from 'k6'; @@ -78,7 +82,7 @@ Let’s walk through creating a simple test which performs 10 `GET` HTTP request // The default exported function is gonna be picked up by k6 as the entry point for the test script. It will be executed repeatedly in "iterations" for the whole duration of the test. export default function () { // Make a GET request to the target URL - http.get('https://test-api.k6.io'); + http.get('https://quickpizza.grafana.com'); // Sleep for 1 second to simulate real-world usage sleep(1); diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/_index.md b/docs/sources/k6/next/javascript-api/jslib/httpx/_index.md index 23bc5a629a..288be11261 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/_index.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/_index.md @@ -57,56 +57,58 @@ import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; const USERNAME = `user${randomIntBetween(1, 100000)}@example.com`; // random email address -const PASSWORD = 'superCroc2021'; +const PASSWORD = 'secretpassword'; const session = new Httpx({ - baseURL: 'https://test-api.k6.io', + baseURL: 'https://quickpizza.grafana.com', headers: { 'User-Agent': 'My custom user agent', - 'Content-Type': 'application/x-www-form-urlencoded', }, timeout: 20000, // 20s timeout. }); export default function testSuite() { - const registrationResp = session.post(`/user/register/`, { - first_name: 'Crocodile', - last_name: 'Owner', - username: USERNAME, - password: PASSWORD, - }); + const registrationResp = session.post( + `/api/users`, + JSON.stringify({ + username: USERNAME, + password: PASSWORD, + }) + ); if (registrationResp.status !== 201) { fail('registration failed'); } - const loginResp = session.post(`/auth/token/login/`, { - username: USERNAME, - password: PASSWORD, - }); + const loginResp = session.post( + `/api/users/token/login`, + JSON.stringify({ + username: USERNAME, + password: PASSWORD, + }) + ); if (loginResp.status !== 200) { fail('Authentication failed'); } - const authToken = loginResp.json('access'); + const authToken = loginResp.json('token'); // set the authorization header on the session for the subsequent requests. session.addHeader('Authorization', `Bearer ${authToken}`); const payload = { - name: `Croc Name`, - sex: 'M', - date_of_birth: '2019-01-01', + stars: 5, + pizza_id: 1, }; // this request uses the Authorization header set above. - const respCreateCrocodile = session.post(`/my/crocodiles/`, payload); + const respCreateRating = session.post(`/api/ratings`, JSON.stringify(payload)); - if (respCreateCrocodile.status !== 201) { - fail('Crocodile creation failed'); + if (respCreateRating.status !== 201) { + fail('Rating creation failed'); } else { - console.log('New crocodile created'); + console.log('New rating created'); } } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/addheader.md b/docs/sources/k6/next/javascript-api/jslib/httpx/addheader.md index e662c8c4ea..b761fadfff 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/addheader.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/addheader.md @@ -19,12 +19,12 @@ weight: 21 ```javascript import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; -const session = new Httpx({ baseURL: 'https://test-api.k6.io' }); +const session = new Httpx({ baseURL: 'https://quickpizza.grafana.com' }); -session.addHeader('Authorization', 'token1'); +session.addHeader('Authorization', 'token abcdef0123456789'); export default function () { - session.get('/public/crocodiles/1/'); + session.get('/api/ratings'); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/addheaders.md b/docs/sources/k6/next/javascript-api/jslib/httpx/addheaders.md index 0c3994262b..67021efceb 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/addheaders.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/addheaders.md @@ -21,13 +21,12 @@ import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; const session = new Httpx(); session.addHeaders({ - 'Authorization': 'token1', + 'Authorization': 'token abcdef0123456789', 'User-Agent': 'My custom user agent', - 'Content-Type': 'application/x-www-form-urlencoded', }); export default function () { - session.get('https://test-api.k6.io/public/crocodiles/1/'); + session.get('https://quickpizza.grafana.com/api/ratings'); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/addtag.md b/docs/sources/k6/next/javascript-api/jslib/httpx/addtag.md index cea24539bb..69269a7b7d 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/addtag.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/addtag.md @@ -19,13 +19,13 @@ weight: 24 ```javascript import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; -const session = new Httpx({ baseURL: 'https://test-api.k6.io' }); +const session = new Httpx({ baseURL: 'https://quickpizza.grafana.com' }); session.addTag('tagName', 'tagValue'); session.addTag('AnotherTagName', 'tagValue2'); export default function () { - session.get('/public/crocodiles/1/'); + session.get('/'); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/addtags.md b/docs/sources/k6/next/javascript-api/jslib/httpx/addtags.md index ed87dbdb6c..76c6ff420b 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/addtags.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/addtags.md @@ -27,7 +27,7 @@ session.addTags({ }); export default function () { - session.get('https://test-api.k6.io/public/crocodiles/1/'); + session.get('https://quickpizza.grafana.com'); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/batch.md b/docs/sources/k6/next/javascript-api/jslib/httpx/batch.md index 34ea828f01..5bf29872ce 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/batch.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/batch.md @@ -29,19 +29,16 @@ Batch multiple HTTP requests together, to issue them in parallel over multiple T import { Httpx, Get } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; import { describe } from 'https://jslib.k6.io/expect/0.0.4/index.js'; -const session = new Httpx({ baseURL: 'https://test-api.k6.io' }); +const session = new Httpx({ baseURL: 'https://quickpizza.grafana.com' }); + +session.addHeader('Authorization', 'token abcdef0123456789'); export default function () { - describe('01. Fetch public crocodiles all at once', (t) => { + describe('01. Fetch public ratings all at once', (t) => { const responses = session.batch( - [ - new Get('/public/crocodiles/1/'), - new Get('/public/crocodiles/2/'), - new Get('/public/crocodiles/3/'), - new Get('/public/crocodiles/4/'), - ], + [new Get('/api/ratings/1'), new Get('/api/ratings/2'), new Get('/api/ratings/3')], { - tags: { name: 'PublicCrocs' }, + tags: { name: 'PublicRatings' }, } ); @@ -51,9 +48,9 @@ export default function () { .toEqual(200) .and(response) .toHaveValidJson() - .and(response.json('age')) - .as('croc age') - .toBeGreaterThan(7); + .and(response.json('stars')) + .as('rating stars') + .toBeGreaterThan(0); }); }); } diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/clearheader.md b/docs/sources/k6/next/javascript-api/jslib/httpx/clearheader.md index 721dfaaaad..69eec4a6bf 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/clearheader.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/clearheader.md @@ -23,7 +23,7 @@ const session = new Httpx({ headers: { Authorization: 'token1' } }); session.clearHeader('Authorization'); // removes header set in the constructor export default function () { - session.get('https://test-api.k6.io/public/crocodiles/1/'); + session.get('https://quickpizza.grafana.com'); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/cleartag.md b/docs/sources/k6/next/javascript-api/jslib/httpx/cleartag.md index 5328b36ba5..ac012b5e5b 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/cleartag.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/cleartag.md @@ -23,7 +23,7 @@ const session = new Httpx({ tags: { tagName: 'tagValue' } }); session.clearTag('tagName'); // removes tag set in the constructor export default function () { - session.get('https://test-api.k6.io/public/crocodiles/1/'); + session.get('https://quickpizza.grafana.com'); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/get.md b/docs/sources/k6/next/javascript-api/jslib/httpx/get.md index 3af87819b1..0e9f32e212 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/get.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/get.md @@ -29,12 +29,12 @@ weight: 10 import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; const session = new Httpx({ - baseURL: 'https://test-api.k6.io', + baseURL: 'https://quickpizza.grafana.com', timeout: 20000, // 20s timeout. }); export default function testSuite() { - const resp = session.get(`/public/crocodiles/`); + const resp = session.get(`/api/names`); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/post.md b/docs/sources/k6/next/javascript-api/jslib/httpx/post.md index 877284a77b..690ce3ddb5 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/post.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/post.md @@ -28,16 +28,14 @@ weight: 11 import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; const session = new Httpx({ - baseURL: 'https://test-api.k6.io', + baseURL: 'https://quickpizza.grafana.com', timeout: 20000, // 20s timeout. }); export default function testSuite() { - const resp = session.post(`/user/register/`, { + const resp = session.post(`/api/json`, { first_name: 'Mr', last_name: 'Croco', - username: 'my user', - password: 'my password', }); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/httpx/setbaseurl.md b/docs/sources/k6/next/javascript-api/jslib/httpx/setbaseurl.md index 5c0e061308..31ef3cec24 100644 --- a/docs/sources/k6/next/javascript-api/jslib/httpx/setbaseurl.md +++ b/docs/sources/k6/next/javascript-api/jslib/httpx/setbaseurl.md @@ -20,10 +20,10 @@ import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js'; const session = new Httpx(); -session.setBaseUrl('https://test-api.k6.io'); +session.setBaseUrl('https://quickpizza.grafana.com'); export default function () { - session.get('/public/crocodiles/1/'); // baseUrl doesn't need to be repeated on every request + session.get('/api/names'); // baseUrl doesn't need to be repeated on every request } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/k6chaijs/_index.md b/docs/sources/k6/next/javascript-api/jslib/k6chaijs/_index.md index e6e05cf257..2b82308774 100644 --- a/docs/sources/k6/next/javascript-api/jslib/k6chaijs/_index.md +++ b/docs/sources/k6/next/javascript-api/jslib/k6chaijs/_index.md @@ -45,12 +45,12 @@ import http from 'k6/http'; import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js'; export default function testSuite() { - describe('Fetch a list of public crocodiles', () => { - const response = http.get('https://test-api.k6.io/public/crocodiles'); + describe('Fetch a list of pizza names', () => { + const response = http.get('https://quickpizza.grafana.com/api/names'); expect(response.status, 'response status').to.equal(200); expect(response).to.have.validJsonBody(); - expect(response.json().length, 'number of crocs').to.be.above(4); + expect(response.json('names').length, 'number of names').to.be.above(1); }); } ``` diff --git a/docs/sources/k6/next/javascript-api/jslib/k6chaijs/config.md b/docs/sources/k6/next/javascript-api/jslib/k6chaijs/config.md index 514f014831..00e52811c4 100644 --- a/docs/sources/k6/next/javascript-api/jslib/k6chaijs/config.md +++ b/docs/sources/k6/next/javascript-api/jslib/k6chaijs/config.md @@ -35,19 +35,19 @@ chai.config.logFailures = true; export default function testSuite() { describe('Testing bad assertion.', () => { - const response = http.get('https://test-api.k6.io/'); + const response = http.get('https://quickpizza.grafana.com'); - expect(response.body).to.have.lengthOf.at.least(500); + expect(response.body).to.have.lengthOf.at.least(100); }); } ``` {{< /code >}} -The resulting +Resulting in: ```bash █ Testing bad assertion. ✓ expected '\n\n\n\n\n\n}} + + ```javascript import { check, group } from 'k6'; import http from 'k6/http'; export default function () { - group('Fetch a list of public crocodiles', () => { - const res = http.get('https://test-api.k6.io/public/crocodiles'); + group('Fetch a list of pizza names', () => { + const res = http.get('https://quickpizza.grafana.com/api/names'); check(res, { 'is status 200': (r) => r.status === 200, 'got more than 5 crocs': (r) => r.json().length > 5, @@ -62,6 +64,8 @@ Sometimes it's hard to predict how a SUT might fail. For those cases, [describe] {{< code >}} + + ```javascript import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js'; diff --git a/docs/sources/k6/next/javascript-api/jslib/utils/_index.md b/docs/sources/k6/next/javascript-api/jslib/utils/_index.md index f832cd686b..5b0fc7bc22 100644 --- a/docs/sources/k6/next/javascript-api/jslib/utils/_index.md +++ b/docs/sources/k6/next/javascript-api/jslib/utils/_index.md @@ -44,9 +44,7 @@ import { } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; export default function () { - const res = http.post(`https://test-api.k6.io/user/register/`, { - first_name: randomItem(['Joe', 'Jane']), // random name - last_name: `Jon${randomString(1, 'aeiou')}s`, //random character from given list + const res = http.post(`https://quickpizza.grafana.com/api/users`, { username: `user_${randomString(10)}@example.com`, // random email address, password: uuidv4(), // random password in form of uuid }); diff --git a/docs/sources/k6/next/javascript-api/k6-execution.md b/docs/sources/k6/next/javascript-api/k6-execution.md index e057b8377e..9f59058b4a 100644 --- a/docs/sources/k6/next/javascript-api/k6-execution.md +++ b/docs/sources/k6/next/javascript-api/k6-execution.md @@ -126,6 +126,8 @@ The `name` property can be used for executing the logic based on which script is {{< code >}} + + ```javascript import exec from 'k6/execution'; @@ -157,6 +159,8 @@ Aborting is possible during initialization: {{< code >}} + + ```javascript import exec from 'k6/execution'; exec.test.abort(); @@ -168,6 +172,8 @@ As well as inside the `default` function: {{< code >}} + + ```javascript import exec from 'k6/execution'; @@ -189,6 +195,8 @@ Get the consolidated and derived options' values {{< code >}} + + ```javascript import exec from 'k6/execution'; @@ -222,7 +230,7 @@ export default function () { exec.vu.metrics.tags['mytag2'] = 2; // the metrics these HTTP requests emit will get tagged with `mytag` and `mytag2`: - http.batch(['https://test.k6.io', 'https://test-api.k6.io']); + http.batch(['https://test.k6.io', 'https://quickpizza.grafana.com']); } ``` @@ -244,11 +252,11 @@ export default function () { exec.vu.metrics.metadata['trace_id'] = 'somecoolide'; // the metrics these HTTP requests emit will get the metadata `trace_id`: - http.batch(['https://test.k6.io', 'https://test-api.k6.io']); + http.batch(['https://test.k6.io', 'https://quickpizza.grafana.com']); delete exec.vu.metrics.metadata['trace_id']; // this will unset it // which will make the metrics these requests to not have the metadata `trace_id` set on them. - http.batch(['https://test.k6.io', 'https://test-api.k6.io']); + http.batch(['https://test.k6.io', 'https://quickpizza.grafana.com']); } ``` diff --git a/docs/sources/k6/next/javascript-api/k6-experimental/redis/client/_index.md b/docs/sources/k6/next/javascript-api/k6-experimental/redis/client/_index.md index 4d0bf1d4ff..2272b588be 100644 --- a/docs/sources/k6/next/javascript-api/k6-experimental/redis/client/_index.md +++ b/docs/sources/k6/next/javascript-api/k6-experimental/redis/client/_index.md @@ -225,9 +225,9 @@ export const options = { // Instantiate a new redis client const redisClient = new redis.Client(`redis://localhost:6379`); -// Prepare an array of crocodile ids for later use +// Prepare an array of rating ids for later use // in the context of the measureUsingRedisData function. -const crocodileIDs = new Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); +const ratingIDs = new Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); export async function measureRedisPerformance() { // VUs are executed in a parallel fashion, @@ -250,31 +250,33 @@ export async function measureRedisPerformance() { } export async function setup() { - await redisClient.sadd('crocodile_ids', ...crocodileIDs); + await redisClient.sadd('rating_ids', ...ratingIDs); } export async function measureUsingRedisData() { - // Pick a random crocodile id from the dedicated redis set, + // Pick a random rating id from the dedicated redis set, // we have filled in setup(). - const randomID = await redisClient.srandmember('crocodile_ids'); - const url = `https://test-api.k6.io/public/crocodiles/${randomID}`; - const res = await http.asyncRequest('GET', url); + const randomID = await redisClient.srandmember('rating_ids'); + const url = `https://quickpizza.grafana.com/api/ratings/${randomID}`; + const res = await http.asyncRequest('GET', url, { + headers: { Authorization: 'token abcdef0123456789' }, + }); check(res, { 'status is 200': (r) => r.status === 200 }); - await redisClient.hincrby('k6_crocodile_fetched', url, 1); + await redisClient.hincrby('k6_rating_fetched', url, 1); } export async function teardown() { - await redisClient.del('crocodile_ids'); + await redisClient.del('rating_ids'); } export function handleSummary(data) { redisClient - .hgetall('k6_crocodile_fetched') - .then((fetched) => Object.assign(data, { k6_crocodile_fetched: fetched })) + .hgetall('k6_rating_fetched') + .then((fetched) => Object.assign(data, { k6_rating_fetched: fetched })) .then((data) => redisClient.set(`k6_report_${Date.now()}`, JSON.stringify(data))) - .then(() => redisClient.del('k6_crocodile_fetched')); + .then(() => redisClient.del('k6_rating_fetched')); return { stdout: textSummary(data, { indent: ' ', enableColors: true }), @@ -350,3 +352,5 @@ export function handleSummary(data) { | Method | Description | | :--------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------- | | [`Client.sendCommand(command, args)`](https://grafana.com/docs/k6//javascript-api/k6-experimental/redis/client/client-sendcommand) | Send a command to the Redis server. | + + diff --git a/docs/sources/k6/next/javascript-api/k6-experimental/websockets/_index.md b/docs/sources/k6/next/javascript-api/k6-experimental/websockets/_index.md index 7a521873e3..42680ebbc0 100644 --- a/docs/sources/k6/next/javascript-api/k6-experimental/websockets/_index.md +++ b/docs/sources/k6/next/javascript-api/k6-experimental/websockets/_index.md @@ -62,8 +62,7 @@ This example shows: import { randomString, randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.1.0/index.js'; import { WebSocket } from 'k6/experimental/websockets'; -const chatRoomName = 'publicRoom'; // choose any chat room name -const sessionDuration = randomIntBetween(5000, 60000); // user session between 5s and 1m +const sessionDuration = randomIntBetween(1000, 3000); // user session between 1s and 3s export default function () { for (let i = 0; i < 4; i++) { @@ -73,12 +72,12 @@ export default function () { function startWSWorker(id) { // create a new websocket connection - const ws = new WebSocket(`wss://test-api.k6.io/ws/crocochat/${chatRoomName}/`); + const ws = new WebSocket(`wss://quickpizza.grafana.com/ws`); ws.binaryType = 'arraybuffer'; ws.addEventListener('open', () => { // change the user name - ws.send(JSON.stringify({ event: 'SET_NAME', new_name: `Croc ${__VU}:${id}` })); + ws.send(JSON.stringify({ event: 'SET_NAME', new_name: `VU ${__VU}:${id}` })); // listen for messages/errors and log them into console ws.addEventListener('message', (e) => { diff --git a/docs/sources/k6/next/javascript-api/k6-experimental/websockets/websocket/websocket-onping.md b/docs/sources/k6/next/javascript-api/k6-experimental/websockets/websocket/websocket-onping.md index f805734506..48c3a05079 100644 --- a/docs/sources/k6/next/javascript-api/k6-experimental/websockets/websocket/websocket-onping.md +++ b/docs/sources/k6/next/javascript-api/k6-experimental/websockets/websocket/websocket-onping.md @@ -15,11 +15,13 @@ _A k6 script that initiates a WebSocket connection and sets up a handler for the {{< code >}} + + ```javascript import { WebSocket } from 'k6/experimental/websockets'; export default function () { - const ws = new WebSocket('wss://test-api.k6.io/ws/crocochat/publicRoom/'); + const ws = new WebSocket('wss://quickpizza.grafana.com/ws'); ws.onping = () => { console.log('A ping happened!'); @@ -27,16 +29,16 @@ export default function () { }; ws.onclose = () => { - console.log('WebSocket connection closed!'); - } + console.log('WebSocket connection closed!'); + }; ws.onopen = () => { - ws.send(JSON.stringify({ 'event': 'SET_NAME', 'new_name': `Croc ${__VU}` })); - } + ws.send(JSON.stringify({ event: 'SET_NAME', new_name: `Croc ${__VU}` })); + }; ws.onerror = (err) => { - console.log(err) - } + console.log(err); + }; } ``` -{{< /code >}} \ No newline at end of file +{{< /code >}} diff --git a/docs/sources/k6/next/javascript-api/k6-http/response/response-json.md b/docs/sources/k6/next/javascript-api/k6-http/response/response-json.md index 94cf51edff..6294296954 100644 --- a/docs/sources/k6/next/javascript-api/k6-http/response/response-json.md +++ b/docs/sources/k6/next/javascript-api/k6-http/response/response-json.md @@ -27,7 +27,7 @@ This method takes an object argument where the following properties can be set: import http from 'k6/http'; export default function () { - const res = http.get('https://test-api.k6.io/public/crocodiles/'); + const res = http.get('https://quickpizza.grafana.com/api/json?foo=bar'); console.log(res.json()); } diff --git a/docs/sources/k6/next/javascript-api/k6-metrics/counter/_index.md b/docs/sources/k6/next/javascript-api/k6-metrics/counter/_index.md index 30105182d5..c0e10216b9 100644 --- a/docs/sources/k6/next/javascript-api/k6-metrics/counter/_index.md +++ b/docs/sources/k6/next/javascript-api/k6-metrics/counter/_index.md @@ -53,7 +53,7 @@ const CounterErrors = new Counter('Errors'); export const options = { thresholds: { Errors: ['count<100'] } }; export default function () { - const res = http.get('https://test-api.k6.io/public/crocodiles/1/'); + const res = http.get('https://quickpizza.grafana.com/api/json?name=Bert'); const contentOK = res.json('name') === 'Bert'; CounterErrors.add(!contentOK); } @@ -85,8 +85,8 @@ export const options = { }; export default function () { - const auth_resp = http.post('https://test-api.k6.io/auth/token/login/', { - username: 'test-user', + const auth_resp = http.post('https://quickpizza.grafana.com/api/users/token/login', { + username: 'default', password: 'supersecure', }); @@ -94,7 +94,7 @@ export default function () { allErrors.add(1, { errorType: 'authError' }); // tagged value creates submetric (useful for making thresholds specific) } - const other_resp = http.get('https://test-api.k6.io/public/crocodiles/1/'); + const other_resp = http.get('https://quickpizza.grafana.com/api/json'); if (other_resp.status >= 400) { allErrors.add(1); // untagged value } diff --git a/docs/sources/k6/next/javascript-api/k6-metrics/gauge/_index.md b/docs/sources/k6/next/javascript-api/k6-metrics/gauge/_index.md index 5e38d0625f..6b619795cc 100644 --- a/docs/sources/k6/next/javascript-api/k6-metrics/gauge/_index.md +++ b/docs/sources/k6/next/javascript-api/k6-metrics/gauge/_index.md @@ -59,7 +59,7 @@ export const options = { }; export default function () { - const res = http.get('https://test-api.k6.io/public/crocodiles/1/'); + const res = http.get('https://quickpizza.grafana.com'); GaugeContentSize.add(res.body.length); sleep(1); } diff --git a/docs/sources/k6/next/javascript-api/k6-metrics/rate/_index.md b/docs/sources/k6/next/javascript-api/k6-metrics/rate/_index.md index d19199edcf..555e4766e7 100644 --- a/docs/sources/k6/next/javascript-api/k6-metrics/rate/_index.md +++ b/docs/sources/k6/next/javascript-api/k6-metrics/rate/_index.md @@ -67,7 +67,7 @@ export const options = { }; export default function () { - const resp = http.get('https://test-api.k6.io/public/crocodiles/1/'); + const resp = http.get('https://quickpizza.grafana.com'); errorRate.add(resp.status >= 400); diff --git a/docs/sources/k6/next/javascript-api/k6-metrics/trend/_index.md b/docs/sources/k6/next/javascript-api/k6-metrics/trend/_index.md index 08c7c59860..d14ef18c2c 100644 --- a/docs/sources/k6/next/javascript-api/k6-metrics/trend/_index.md +++ b/docs/sources/k6/next/javascript-api/k6-metrics/trend/_index.md @@ -61,6 +61,8 @@ export default function () { {{< code >}} + + ```javascript import { Trend } from 'k6/metrics'; import { sleep } from 'k6'; @@ -77,8 +79,8 @@ export const options = { }; export default function () { - const resp = http.post('https://test-api.k6.io/auth/token/login/', { - username: 'test-user', + const resp = http.post('https://quickpizza.grafana.com/api/users/token/login', { + username: 'default', password: 'supersecure', }); diff --git a/docs/sources/k6/next/set-up/set-up-distributed-k6/usage/executing-k6-scripts-with-testrun-crd.md b/docs/sources/k6/next/set-up/set-up-distributed-k6/usage/executing-k6-scripts-with-testrun-crd.md index 64a7c626cb..20ddeb3108 100644 --- a/docs/sources/k6/next/set-up/set-up-distributed-k6/usage/executing-k6-scripts-with-testrun-crd.md +++ b/docs/sources/k6/next/set-up/set-up-distributed-k6/usage/executing-k6-scripts-with-testrun-crd.md @@ -82,7 +82,7 @@ import { sleep, check } from 'k6'; import http from 'k6/http'; export default () => { - const res = http.get('https://test-api.k6.io'); + const res = http.get('https://quickpizza.grafana.com'); check(res, { 'status is 200': () => res.status === 200, }); diff --git a/docs/sources/k6/next/testing-guides/test-types/breakpoint-testing.md b/docs/sources/k6/next/testing-guides/test-types/breakpoint-testing.md index b213d535ad..6e6bb43382 100644 --- a/docs/sources/k6/next/testing-guides/test-types/breakpoint-testing.md +++ b/docs/sources/k6/next/testing-guides/test-types/breakpoint-testing.md @@ -87,7 +87,7 @@ export const options = { }; export default () => { - const urlRes = http.get('https://test-api.k6.io'); + const urlRes = http.get('https://quickpizza.grafana.com'); sleep(1); // MORE STEPS // Here you can have more steps or complex script diff --git a/docs/sources/k6/next/testing-guides/test-types/load-testing.md b/docs/sources/k6/next/testing-guides/test-types/load-testing.md index 905f225f6b..06fde74513 100644 --- a/docs/sources/k6/next/testing-guides/test-types/load-testing.md +++ b/docs/sources/k6/next/testing-guides/test-types/load-testing.md @@ -82,7 +82,7 @@ export const options = { }; export default () => { - const urlRes = http.get('https://test-api.k6.io'); + const urlRes = http.get('https://quickpizza.grafana.com'); sleep(1); // MORE STEPS // Here you can have more steps or complex script diff --git a/docs/sources/k6/next/testing-guides/test-types/smoke-testing.md b/docs/sources/k6/next/testing-guides/test-types/smoke-testing.md index ca56f2ca7d..61655e97a7 100644 --- a/docs/sources/k6/next/testing-guides/test-types/smoke-testing.md +++ b/docs/sources/k6/next/testing-guides/test-types/smoke-testing.md @@ -55,7 +55,7 @@ export const options = { }; export default () => { - const urlRes = http.get('https://test-api.k6.io'); + const urlRes = http.get('https://quickpizza.grafana.com'); sleep(1); // MORE STEPS // Here you can have more steps or complex script diff --git a/docs/sources/k6/next/testing-guides/test-types/soak-testing.md b/docs/sources/k6/next/testing-guides/test-types/soak-testing.md index 9b5e89342b..748a341ad3 100644 --- a/docs/sources/k6/next/testing-guides/test-types/soak-testing.md +++ b/docs/sources/k6/next/testing-guides/test-types/soak-testing.md @@ -72,7 +72,7 @@ export const options = { }; export default () => { - const urlRes = http.get('https://test-api.k6.io'); + const urlRes = http.get('https://quickpizza.grafana.com'); sleep(1); // MORE STEPS // Here you can have more steps or complex script diff --git a/docs/sources/k6/next/testing-guides/test-types/spike-testing.md b/docs/sources/k6/next/testing-guides/test-types/spike-testing.md index fa2e29a3ad..63f6d20552 100644 --- a/docs/sources/k6/next/testing-guides/test-types/spike-testing.md +++ b/docs/sources/k6/next/testing-guides/test-types/spike-testing.md @@ -68,7 +68,7 @@ export const options = { }; export default () => { - const urlRes = http.get('https://test-api.k6.io'); + const urlRes = http.get('https://quickpizza.grafana.com'); sleep(1); // MORE STEPS // Add only the processes that will be on high demand diff --git a/docs/sources/k6/next/testing-guides/test-types/stress-testing.md b/docs/sources/k6/next/testing-guides/test-types/stress-testing.md index 8cf097d7de..75b82553e1 100644 --- a/docs/sources/k6/next/testing-guides/test-types/stress-testing.md +++ b/docs/sources/k6/next/testing-guides/test-types/stress-testing.md @@ -72,7 +72,7 @@ export const options = { }; export default () => { - const urlRes = http.get('https://test-api.k6.io'); + const urlRes = http.get('https://quickpizza.grafana.com'); sleep(1); // MORE STEPS // Here you can have more steps or complex script diff --git a/docs/sources/k6/next/testing-guides/use-chai-with-k6.md b/docs/sources/k6/next/testing-guides/use-chai-with-k6.md index f5604e1834..2c9a989191 100644 --- a/docs/sources/k6/next/testing-guides/use-chai-with-k6.md +++ b/docs/sources/k6/next/testing-guides/use-chai-with-k6.md @@ -194,35 +194,39 @@ import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.5.0.1/index.js' import http from 'k6/http'; export default function () { + const headers = { Authorization: 'token abcdef0123456789' }; + describe('crocodiles API', () => { describe('should fetch a list of public crocodiles', () => { - const response = http.get('https://test-api.k6.io/public/crocodiles'); + const response = http.get('https://quickpizza.grafana.com/api/ratings', { headers: headers }); expect(response.status, 'response status').to.equal(200); expect(response).to.have.validJsonBody(); - expect(response.json().length, 'number of crocs').to.be.above(4); + expect(response.json('ratings').length, 'number of ratings').to.be.above(1); }); - describe('should respond with status 200, when a valid user id is provided', () => { + describe('should respond with status 200, when a valid rating id is provided', () => { const expected = { - id: 6, - name: 'Sang Buaya', - sex: 'F', - date_of_birth: '2006-01-28', - age: 16, + id: 1, + stars: 5, + pizza_id: 1, }; - const response = http.get('https://test-api.k6.io/public/crocodiles/6'); + const response = http.get('https://quickpizza.grafana.com/api/ratings/1', { + headers: headers, + }); expect(response.status, 'status').to.equal(200); expect(JSON.parse(response.body), 'response body').to.deep.equal(expected); }); - describe('should respond with status 404, when an invalid user id is provided', () => { - const response = http.get('https://test-api.k6.io/public/crocodiles/9999999'); + describe('should respond with status 404, when an invalid rating id is provided', () => { + const response = http.get('https://quickpizza.grafana.com/api/ratings/12312123123123', { + headers: headers, + }); expect(response.status, 'status').to.equal(404); - expect(JSON.parse(response.body).detail, 'error message').to.equal('Not found.'); + expect(JSON.parse(response.body).error, 'error message').to.contain('not found'); }); }); } diff --git a/docs/sources/k6/next/using-k6/metrics/_index.md b/docs/sources/k6/next/using-k6/metrics/_index.md index 0ec430e263..cffb99774b 100644 --- a/docs/sources/k6/next/using-k6/metrics/_index.md +++ b/docs/sources/k6/next/using-k6/metrics/_index.md @@ -54,7 +54,7 @@ An aggregated summary of all _built-in_ and custom metrics outputs to `stdout` w import http from 'k6/http'; export default function () { - http.get('https://test-api.k6.io/'); + http.get('https://quickpizza.grafana.com'); } ``` @@ -67,11 +67,12 @@ The preceding script outputs something like this: ```bash $ k6 run script.js - /\ |‾‾| /‾‾/ /‾‾/ - /\ / \ | |/ / / / - / \/ \ | ( / ‾‾\ - / \ | |\ \ | (‾) | - / __________ \ |__| \__\ \_____/ .io + /\ Grafana /‾‾/ + /\ / \ |\ __ / / + / \/ \ | |/ / / ‾‾\ + / \ | ( | (‾) | + / __________ \ |_|\_\ \_____/ + execution: local script: http_get.js diff --git a/docs/sources/k6/next/using-k6/protocols/http-2.md b/docs/sources/k6/next/using-k6/protocols/http-2.md index e27aeb6b7a..449ecab8a1 100644 --- a/docs/sources/k6/next/using-k6/protocols/http-2.md +++ b/docs/sources/k6/next/using-k6/protocols/http-2.md @@ -37,7 +37,7 @@ import http from 'k6/http'; import { check, sleep } from 'k6'; export default function () { - const res = http.get('https://test-api.k6.io/'); + const res = http.get('https://quickpizza.grafana.com'); check(res, { 'protocol is HTTP/2': (r) => r.proto === 'HTTP/2.0', }); diff --git a/docs/sources/k6/next/using-k6/scenarios/advanced-examples.md b/docs/sources/k6/next/using-k6/scenarios/advanced-examples.md index 71e75b89e0..49abe8fa36 100644 --- a/docs/sources/k6/next/using-k6/scenarios/advanced-examples.md +++ b/docs/sources/k6/next/using-k6/scenarios/advanced-examples.md @@ -29,6 +29,8 @@ Along with `startTime`, `duration`, and `maxDuration`, note the different test l {{< code >}} + + ```javascript import http from 'k6/http'; @@ -38,16 +40,16 @@ export const options = { contacts: { executor: 'constant-vus', exec: 'contacts', - vus: 50, - duration: '30s', + vus: 5, + duration: '5s', }, news: { executor: 'per-vu-iterations', exec: 'news', - vus: 50, - iterations: 100, - startTime: '30s', - maxDuration: '1m', + vus: 5, + iterations: 10, + startTime: '10s', + maxDuration: '20s', }, }, }; @@ -73,6 +75,8 @@ But, you can also set tags per scenario, which applies them to other {{< code >}} + + ```javascript import http from 'k6/http'; import { fail } from 'k6'; @@ -135,6 +139,8 @@ This test has 3 scenarios, each with different `exec` functions, tags and enviro {{< code >}} + + ```javascript import http from 'k6/http'; import { sleep } from 'k6'; @@ -194,7 +200,7 @@ export function webtest() { } export function apitest() { - http.get(`https://test-api.k6.io/public/crocodiles/${__ENV.MY_CROC_ID}/`); + http.get(`https://quickpizza.grafana.com/api/json?crocId=${__ENV.MY_CROC_ID}`); // no need for sleep() here, the iteration pacing will be controlled by the // arrival-rate executors above! } @@ -239,7 +245,7 @@ export const options = { }; export default function () { - const response = http.get('https://test-api.k6.io/public/crocodiles/'); + const response = http.get('https://quickpizza.grafana.com'); } ``` diff --git a/docs/sources/k6/next/using-k6/tags-and-groups.md b/docs/sources/k6/next/using-k6/tags-and-groups.md index cc56e00329..2e8671f830 100644 --- a/docs/sources/k6/next/using-k6/tags-and-groups.md +++ b/docs/sources/k6/next/using-k6/tags-and-groups.md @@ -151,7 +151,7 @@ export default function () { group('sub', function () { http.get('https://quickpizza.grafana.com/'); }); - http.get('https://test-api.k6.io'); + http.get('https://quickpizza.grafana.com/api/headers'); }); delete exec.vu.tags.containerGroup; diff --git a/docs/sources/k6/next/using-k6/thresholds.md b/docs/sources/k6/next/using-k6/thresholds.md index 634f702bda..e922e06753 100644 --- a/docs/sources/k6/next/using-k6/thresholds.md +++ b/docs/sources/k6/next/using-k6/thresholds.md @@ -35,7 +35,7 @@ The other evaluates whether 95 percent of responses happen within a certain dura {{< code >}} - + ```javascript import http from 'k6/http'; @@ -48,7 +48,7 @@ export const options = { }; export default function () { - http.get('https://test-api.k6.io/public/crocodiles/1/'); + http.get('https://quickpizza.grafana.com'); } ``` @@ -152,7 +152,7 @@ setting different types of thresholds for each: {{< code >}} - + ```javascript import http from 'k6/http'; @@ -178,7 +178,7 @@ export const options = { }; export default function () { - const res = http.get('https://test-api.k6.io/public/crocodiles/1/'); + const res = http.get('https://quickpizza.grafana.com/api/json?name=Bert'); const contentOK = res.json('name') === 'Bert'; TrendRTT.add(res.timings.duration); @@ -230,6 +230,8 @@ For more specific threshold examples, refer to the [Counter](https://grafana.com {{< code >}} + + ```javascript import http from 'k6/http'; import { sleep } from 'k6'; @@ -242,7 +244,7 @@ export const options = { }; export default function () { - http.get('https://test-api.k6.io/public/crocodiles/1/'); + http.get('https://quickpizza.grafana.com'); sleep(1); } ``` @@ -253,6 +255,8 @@ export default function () { {{< code >}} + + ```javascript import http from 'k6/http'; import { sleep } from 'k6'; @@ -265,7 +269,7 @@ export const options = { }; export default function () { - http.get('https://test-api.k6.io/public/crocodiles/1/'); + http.get('https://quickpizza.grafana.com'); sleep(1); } ``` @@ -279,6 +283,8 @@ This threshold has different duration requirements for different request percent {{< code >}} + + ```javascript import http from 'k6/http'; import { sleep } from 'k6'; @@ -291,7 +297,7 @@ export const options = { }; export default function () { - const res1 = http.get('https://test-api.k6.io/public/crocodiles/1/'); + const res1 = http.get('https://quickpizza.grafana.com'); sleep(1); } ``` @@ -306,7 +312,7 @@ For each group, there are different thresholds. {{< code >}} - + ```javascript import http from 'k6/http'; @@ -323,16 +329,16 @@ export const options = { export default function () { group('individualRequests', function () { - http.get('https://test-api.k6.io/public/crocodiles/1/'); - http.get('https://test-api.k6.io/public/crocodiles/2/'); - http.get('https://test-api.k6.io/public/crocodiles/3/'); + http.get('https://quickpizza.grafana.com/api/json?letter=a'); + http.get('https://quickpizza.grafana.com/api/json?letter=b'); + http.get('https://quickpizza.grafana.com/api/json?letter=c'); }); group('batchRequests', function () { http.batch([ - ['GET', `https://test-api.k6.io/public/crocodiles/1/`], - ['GET', `https://test-api.k6.io/public/crocodiles/2/`], - ['GET', `https://test-api.k6.io/public/crocodiles/3/`], + ['GET', 'https://quickpizza.grafana.com/api/json?letter=a'], + ['GET', 'https://quickpizza.grafana.com/api/json?letter=b'], + ['GET', 'https://quickpizza.grafana.com/api/json?letter=c'], ]); }); @@ -361,7 +367,7 @@ And here's a full example. {{< code >}} - + ```javascript import http from 'k6/http'; @@ -376,21 +382,21 @@ export const options = { }; export default function () { - const res1 = http.get('https://test-api.k6.io/public/crocodiles/1/', { + const res1 = http.get('https://quickpizza.grafana.com/api/headers', { tags: { type: 'API' }, }); - const res2 = http.get('https://test-api.k6.io/public/crocodiles/2/', { + const res2 = http.get('https://quickpizza.grafana.com/api/json', { tags: { type: 'API' }, }); const responses = http.batch([ - ['GET', 'https://test-api.k6.io/static/favicon.ico', null, { tags: { type: 'staticContent' } }], [ 'GET', - 'https://test-api.k6.io/static/css/site.css', + 'https://quickpizza.grafana.com/favicon.ico', null, { tags: { type: 'staticContent' } }, ], + ['GET', 'https://quickpizza.grafana.com/admin', null, { tags: { type: 'staticContent' } }], ]); sleep(1); @@ -443,7 +449,7 @@ Here is an example: {{< code >}} - + ```javascript import http from 'k6/http'; @@ -457,7 +463,7 @@ export const options = { }; export default function () { - http.get('https://test-api.k6.io/public/crocodiles/1/'); + http.get('https://quickpizza.grafana.com'); } ``` diff --git a/scripts/md-k6.py b/scripts/md-k6.py index 816bfa29bf..41509776a8 100644 --- a/scripts/md-k6.py +++ b/scripts/md-k6.py @@ -10,17 +10,17 @@ import hashlib import argparse import subprocess -import textwrap import tempfile from collections import namedtuple -Script = namedtuple("Script", ["text", "options", "env"]) +Script = namedtuple("Script", ["text", "options", "env", "text_hash"]) SKIP = "skip" SKIP_ALL = "skipall" NO_FAIL = "nofail" ENV = "env." FIXED_SCENARIOS = "fixedscenarios" +NO_THRESHOLDS = "nothresholds" def has_browser_scenario(script: Script) -> bool: @@ -29,6 +29,14 @@ def has_browser_scenario(script: Script) -> bool: return "browser" in script.text and "chromium" in script.text +def print_code(script: Script): + """Print the script with line numbers for quick debugging.""" + lines = script.text.splitlines() + for i, line in enumerate(lines): + line = f" {i + 1: >3} {line}" + print(line) + + def run_k6(script: Script, duration: str | None, verbose: bool) -> None: script_file = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".js") script_file.write(script.text) @@ -61,8 +69,20 @@ def run_k6(script: Script, duration: str | None, verbose: bool) -> None: # `fixedscenarios` option. cmd.extend(["-d", duration]) + if NO_THRESHOLDS in script.options: + cmd.append("--no-thresholds") + env = {**os.environ, **script.env} - result = subprocess.run(cmd, env=env) + + print("\nExecuting:", " ".join(cmd)) + if script.env: + print("Env:", script.env) + + try: + result = subprocess.run(cmd, env=env) + except KeyboardInterrupt: + print("Execution cancelled by user.") + result = subprocess.CompletedProcess(None, returncode=1) if result.returncode: print("k6 returned non-zero status:", result.returncode) @@ -70,7 +90,7 @@ def run_k6(script: Script, duration: str | None, verbose: bool) -> None: with open(logs_file.name) as f: logs = f.read() - print("logs:") + print("Logs:") print(logs) except Exception: # Ignore exceptions if we fail to read the logs @@ -88,7 +108,7 @@ def run_k6(script: Script, duration: str | None, verbose: bool) -> None: line = line.strip() parsed = json.loads(line) if parsed["level"] == "error": - print("error in k6 script execution:", line) + print("Error in k6 script execution:", line) if NO_FAIL not in script.options: exit(1) @@ -148,15 +168,18 @@ def main() -> None: # # This is done for the entire Markdown file. # After that's done, we can split the text by "```javascript", and parse - # each part separately. If a part's first line starts with "$", then we + # each part separately. If a part's first line contains a "$", then we # know one or more options were specified by the user (such as "skip"). # - # Additionally, we also skip over any "" comments, to - # allow developers to use both md-k6 *and* ESLint skip directives in code - # blocks. + # Additionally, we also skip over any unrelated "" comments, + # to allow developers to use both md-k6 *and* e.g. ESLint directives in + # code blocks. Caveat: the md-k6 comment needs to be the first one, if + # multiple comments are present before the code block. + # + # Some " *" and "\n+" are added in to skip any present whitespace. text = re.sub( - r"\n+\s*(\n+)?\s*```" + lang, + r"\n+\s*(\n+)?\s*```" + lang, "```" + lang + "$" + r"\1", text, ) @@ -185,7 +208,15 @@ def main() -> None: key, value = opt.removeprefix(ENV).split("=") env[key] = value - scripts.append(Script(text="\n".join(lines[1:]), options=options, env=env)) + script_text = "\n".join(lines[1:]) + scripts.append( + Script( + text=script_text, + options=options, + env=env, + text_hash=hashlib.sha256(script_text.encode("utf-8")).hexdigest()[:16], + ) + ) if ":" in args.blocks: range_parts = args.blocks.split(":") @@ -210,14 +241,14 @@ def main() -> None: exit(1) print("Number of code blocks (scripts) read:", len(scripts)) - print("Number of code blocks (scripts) to run:", len(scripts[start:end])) + to_run = len(scripts[start:end]) + print("Number of code blocks (scripts) to run:", to_run) for i, script in enumerate(scripts[start:end]): - script_hash = hashlib.sha256(script.text.encode("utf-8")).hexdigest()[:16] print( - f"Running script #{i} (hash: {script_hash}, options: {script.options}):\n" + f"Running script #{i + 1} (of {to_run}) (hash: {script.text_hash}, options: {script.options}):\n" ) - print(textwrap.indent(script.text, " ")) + print_code(script) run_k6(script, args.duration, args.verbose) print()