Skip to content

Commit 93ca95c

Browse files
Update the "Using k6 browser" section (#1924)
* Update k6 browser homepage to use k6 new command * Create a write your first browser test page * Fix alias on using k6 browser homepage * Fix weight on k6 browser page section * Add section for how to write k6 browser tests * Move interact with elements on a webpage to its own page * Move asynchronous operation section to a separate page * Update hybrid performance testing page Move some of the content that was in the Running browser tests page to Hybrid approach to performance * Update page weight * Update recommended practices weight * Update Asynchronous operations page Add changes based on Ankur's feedback, update page to mention most browser methods return promises. Add example of using waitFor * Skip code block * Skip code blocks * Update Write your first browser test Add content to Write your first browser test based on the Write your first test page, and the k6 new browser template script * Fix hybrid performance code example * Skip code block * Update docs/sources/k6/next/using-k6-browser/how-to-write-browser-tests/asynchronous-operations.md Co-authored-by: Ankur <[email protected]> * Add heading link to JavaScript promises * Add small example at the top of the page * Update docs/sources/k6/next/using-k6-browser/how-to-write-browser-tests/asynchronous-operations.md * Apply to v1.1 * Apply to v1.0 --------- Co-authored-by: Ankur <[email protected]>
1 parent e0ebcdf commit 93ca95c

33 files changed

+1682
-923
lines changed
Lines changed: 80 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
aliases:
3-
- ./examples/crawl-webpage # docs/k6/<K6_VERSION>/examples/crawl-webpage
3+
- ./using-k6-browser # docs/k6/<K6_VERSION>/using-k6-browser
44
title: Using k6 browser
55
description: 'The browser module brings browser automation and end-to-end testing to k6 while supporting core k6 features. Interact with real browsers and collect frontend metrics as part of your k6 tests.'
66
weight: 300
@@ -31,58 +31,26 @@ The main use case for the browser module is to test performance on the browser l
3131
- Are all my elements interactive on the frontend?
3232
- Are there any loading spinners that take a long time to disappear?
3333

34-
## A simple browser test
35-
36-
{{< code >}}
37-
38-
```javascript
39-
import { browser } from 'k6/browser';
40-
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
41-
42-
export const options = {
43-
scenarios: {
44-
ui: {
45-
executor: 'shared-iterations',
46-
options: {
47-
browser: {
48-
type: 'chromium',
49-
},
50-
},
51-
},
52-
},
53-
thresholds: {
54-
checks: ['rate==1.0'],
55-
},
56-
};
57-
58-
export default async function () {
59-
const context = await browser.newContext();
60-
const page = await context.newPage();
61-
62-
try {
63-
await page.goto('https://test.k6.io/my_messages.php');
64-
65-
await page.locator('input[name="login"]').type('admin');
66-
await page.locator('input[name="password"]').type('123');
67-
68-
await Promise.all([page.waitForNavigation(), page.locator('input[type="submit"]').click()]);
69-
70-
await check(page.locator('h2'), {
71-
header: async (h2) => (await h2.textContent()) == 'Welcome, admin!',
72-
});
73-
} finally {
74-
await page.close();
75-
}
76-
}
77-
```
34+
## Create a browser test
35+
36+
To create a browser test, you first need to:
7837

79-
{{< /code >}}
38+
- [Install k6](https://grafana.com/docs/k6/<K6_VERSION>/set-up/install-k6/) in your machine.
39+
- Install a Chromium-based browser, such as Google Chrome, in your machine.
8040

81-
The preceding code launches a Chromium-based browser, visits the application and mimics a user logging in to the application. Once submitted, it checks if the text of the header matches what is expected.
41+
After, run the `k6 new` command with the `--template` option set to `browser`:
8242

83-
After running the test, the following [browser metrics](https://grafana.com/docs/k6/<K6_VERSION>/using-k6-browser/metrics) will be reported.
43+
```bash
44+
k6 new --template browser browser-script.js
45+
```
8446

85-
{{< code >}}
47+
The command creates a test script you can run right away with the `k6 run` command:
48+
49+
```bash
50+
k6 run browser-script.js
51+
```
52+
53+
After running the test, you can see the [end of test results](https://grafana.com/docs/k6/<K6_VERSION>/results-output/end-of-test/). It contains metrics that show the performance of the website on the script.
8654

8755
```bash
8856
/\ Grafana /‾‾/
@@ -91,38 +59,69 @@ After running the test, the following [browser metrics](https://grafana.com/docs
9159
/ \ | ( | (‾) |
9260
/ __________ \ |_|\_\ \_____/
9361

94-
execution: local
95-
script: test.js
96-
output: -
97-
98-
scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
99-
* default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
100-
101-
102-
running (00m01.3s), 0/1 VUs, 1 complete and 0 interrupted iterations
103-
ui ✓ [======================================] 1 VUs 00m01.3s/10m0s 1/1 shared iters
104-
105-
✓ header
106-
107-
browser_data_received.......: 2.6 kB 2.0 kB/s
108-
browser_data_sent...........: 1.9 kB 1.5 kB/s
109-
browser_http_req_duration...: avg=215.4ms min=124.9ms med=126.65ms max=394.64ms p(90)=341.04ms p(95)=367.84ms
110-
browser_http_req_failed.....: 0.00% ✓ 0 ✗ 3
111-
browser_web_vital_cls.......: avg=0 min=0 med=0 max=0 p(90)=0 p(95)=0
112-
browser_web_vital_fcp.......: avg=344.15ms min=269.2ms med=344.15ms max=419.1ms p(90)=404.11ms p(95)=411.6ms
113-
browser_web_vital_fid.......: avg=200µs min=200µs med=200µs max=200µs p(90)=200µs p(95)=200µs
114-
browser_web_vital_inp.......: avg=8ms min=8ms med=8ms max=8ms p(90)=8ms p(95)=8ms
115-
browser_web_vital_lcp.......: avg=419.1ms min=419.1ms med=419.1ms max=419.1ms p(90)=419.1ms p(95)=419.1ms
116-
browser_web_vital_ttfb......: avg=322.4ms min=251ms med=322.4ms max=393.8ms p(90)=379.52ms p(95)=386.66ms
117-
✓ checks......................: 100.00% ✓ 1 ✗ 0
118-
data_received...............: 0 B 0 B/s
119-
data_sent...................: 0 B 0 B/s
120-
iteration_duration..........: avg=1.28s min=1.28s med=1.28s max=1.28s p(90)=1.28s p(95)=1.28s
121-
iterations..................: 1 0.777541/s
122-
vus.........................: 1 min=1 max=1
123-
vus_max.....................: 1 min=1 max=1
62+
execution: local
63+
script: script.js
64+
output: -
65+
66+
scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
67+
* ui: 1 iterations shared among 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
68+
69+
70+
71+
█ TOTAL RESULTS
72+
73+
checks_total.......................: 2 0.300669/s
74+
checks_succeeded...................: 100.00% 2 out of 2
75+
checks_failed......................: 0.00% 0 out of 2
76+
77+
✓ header
78+
✓ recommendation
79+
80+
HTTP
81+
http_req_duration.......................................................: avg=122ms min=122ms med=122ms max=122ms p(90)=122ms p(95)=122ms
82+
{ expected_response:true }............................................: avg=122ms min=122ms med=122ms max=122ms p(90)=122ms p(95)=122ms
83+
http_req_failed.........................................................: 0.00% 0 out of 1
84+
http_reqs...............................................................: 1 0.150334/s
85+
86+
EXECUTION
87+
iteration_duration......................................................: avg=4.39s min=4.39s med=4.39s max=4.39s p(90)=4.39s p(95)=4.39s
88+
iterations..............................................................: 1 0.150334/s
89+
vus.....................................................................: 1 min=0 max=1
90+
vus_max.................................................................: 1 min=1 max=1
91+
92+
NETWORK
93+
data_received...........................................................: 6.9 kB 1.0 kB/s
94+
data_sent...............................................................: 543 B 82 B/s
95+
96+
BROWSER
97+
browser_data_received...................................................: 357 kB 54 kB/s
98+
browser_data_sent.......................................................: 4.9 kB 738 B/s
99+
browser_http_req_duration...............................................: avg=355.28ms min=124.04ms med=314.4ms max=1.45s p(90)=542.75ms p(95)=753.09ms
100+
browser_http_req_failed.................................................: 0.00% 0 out of 18
101+
102+
WEB_VITALS
103+
browser_web_vital_cls...................................................: avg=0 min=0 med=0 max=0 p(90)=0 p(95)=0
104+
browser_web_vital_fcp...................................................: avg=2.33s min=2.33s med=2.33s max=2.33s p(90)=2.33s p(95)=2.33s
105+
browser_web_vital_fid...................................................: avg=300µs min=300µs med=300µs max=300µs p(90)=300µs p(95)=300µs
106+
browser_web_vital_inp...................................................: avg=56ms min=56ms med=56ms max=56ms p(90)=56ms p(95)=56ms
107+
browser_web_vital_lcp...................................................: avg=2.33s min=2.33s med=2.33s max=2.33s p(90)=2.33s p(95)=2.33s
108+
browser_web_vital_ttfb..................................................: avg=1.45s min=1.45s med=1.45s max=1.45s p(90)=1.45s p(95)=1.45s
124109
```
125110
126-
{{< /code >}}
111+
You can also see at the end of the output the browser and Web Vital metrics that report performance specific to browser testing.
127112
128-
This gives you a representation of browser performance, via the web vitals, as well as the HTTP requests that came from the browser.
113+
```bash
114+
BROWSER
115+
browser_data_received.........: 357 kB 54 kB/s
116+
browser_data_sent.............: 4.9 kB 738 B/s
117+
browser_http_req_duration.....: avg=355.28ms min=124.04ms med=314.4ms max=1.45s p(90)=542.75ms p(95)=753.09ms
118+
browser_http_req_failed.......: 0.00% 0 out of 18
119+
120+
WEB_VITALS
121+
browser_web_vital_cls.........: avg=0 min=0 med=0 max=0 p(90)=0 p(95)=0
122+
browser_web_vital_fcp.........: avg=2.33s min=2.33s med=2.33s max=2.33s p(90)=2.33s p(95)=2.33s
123+
browser_web_vital_fid.........: avg=300µs min=300µs med=300µs max=300µs p(90)=300µs p(95)=300µs
124+
browser_web_vital_inp.........: avg=56ms min=56ms med=56ms max=56ms p(90)=56ms p(95)=56ms
125+
browser_web_vital_lcp.........: avg=2.33s min=2.33s med=2.33s max=2.33s p(90)=2.33s p(95)=2.33s
126+
browser_web_vital_ttfb........: avg=1.45s min=1.45s med=1.45s max=1.45s p(90)=1.45s p(95)=1.45s
127+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: How to write browser tests
3+
description: 'Learn how to write k6 browser tests.'
4+
weight: 300
5+
---
6+
7+
# How to write browser tests
8+
9+
{{< section >}}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
---
2+
title: Asynchronous operations
3+
description: 'Learn how the k6 browser module uses asynchronous operations.'
4+
weight: 100
5+
---
6+
7+
# Asynchronous operations
8+
9+
Most methods in the browser module return [JavaScript promises](#why-the-browser-module-uses-asynchronous-apis), and k6 scripts must be written to handle this properly. This usually means using the `await` keyword to wait for the async operation to complete.
10+
11+
For example:
12+
13+
<!-- eslint-skip -->
14+
15+
```js
16+
const page = await browser.newPage();
17+
18+
await page.goto('https://quickpizza.grafana.com/');
19+
20+
const locator = page.locator('button[name="pizza-please"]');
21+
22+
await locator.click();
23+
```
24+
25+
In addition to using `await`, another important part of writing k6 browser tests is handling page navigations. There are two recommended methods for doing that: using `Promise.all` or using the `waitFor` method.
26+
27+
## Promise.all
28+
29+
To avoid timing errors or other race conditions in your script, if you have actions that load up a different page, you need to make sure that you wait for that action to finish before continuing.
30+
31+
{{< code >}}
32+
33+
```javascript
34+
import { browser } from 'k6/browser';
35+
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';
36+
37+
export const options = {
38+
scenarios: {
39+
ui: {
40+
executor: 'shared-iterations',
41+
options: {
42+
browser: {
43+
type: 'chromium',
44+
},
45+
},
46+
},
47+
},
48+
thresholds: {
49+
checks: ['rate==1.0'],
50+
},
51+
};
52+
53+
export default async function () {
54+
const page = await browser.newPage();
55+
56+
try {
57+
await page.goto('https://test.k6.io/my_messages.php');
58+
59+
await page.locator('input[name="login"]').type('admin');
60+
await page.locator('input[name="password"]').type('123');
61+
62+
const submitButton = page.locator('input[type="submit"]');
63+
64+
await Promise.all([page.waitForNavigation(), submitButton.click()]);
65+
66+
await check(page.locator('h2'), {
67+
header: async (lo) => (await lo.textContent()) == 'Welcome, admin!',
68+
});
69+
} finally {
70+
await page.close();
71+
}
72+
}
73+
```
74+
75+
{{< /code >}}
76+
77+
The preceding code uses `Promise.all([])` to wait for the two promises to be resolved before continuing. Since clicking the submit button causes page navigation, `page.waitForNavigation()` is needed because the page won't be ready until the navigation completes. This is required because there can be a race condition if these two actions don't happen simultaneously.
78+
79+
Then, you can use [`check`](https://grafana.com/docs/k6/<K6_VERSION>/javascript-api/k6/check) from the k6 API to assert the text content of a specific element. Finally, you close the page and the browser.
80+
81+
## Wait for specific elements
82+
83+
We also encourage the use of `locator.waitFor` where possible. When you navigate to a website, there are usually one or more elements that are important for your test. Once those elements load, you can safely proceed to the next step. For example, in a search scenario:
84+
85+
1. Navigate to the search site
86+
1. Wait for the search bar and submit button to appear
87+
1. Fill in the search bar with the query
88+
1. Click the submit button
89+
1. Wait for the search results
90+
91+
We should be able to do these actions like so:
92+
93+
<!-- eslint-skip -->
94+
95+
```js
96+
await page.goto('https://my-search-engine.com');
97+
98+
const searchBar = page.locator('.search-bar');
99+
const submitButton = page.locator('.submit-button');
100+
101+
await searchBar.waitFor();
102+
await submitButton.waitFor();
103+
104+
await searchBar.fill('k6');
105+
await submitButton.click();
106+
107+
const searchResults = page.locator('.search-results-table');
108+
109+
await searchResults.waitFor();
110+
```
111+
112+
This avoids the use of `Promise.all` which can be confusing to work with, and instead makes the script easier to follow.
113+
114+
## Why the browser module uses asynchronous APIs
115+
116+
The browser module uses asynchronous APIs that require `await` for several reasons:
117+
118+
1. JavaScript is single-threaded with a single event loop. Asynchronous APIs prevent blocking the thread and event loop with long-running or I/O-based tasks.
119+
1. Consistency with [Playwright](https://playwright.dev/), a popular browser automation library.
120+
1. Alignment with how developers expect to work with modern JavaScript APIs.
121+
122+
For example:
123+
124+
<!-- eslint-skip -->
125+
126+
```js
127+
const page = await browser.newPage();
128+
129+
await page.goto('https://quickpizza.grafana.com/');
130+
131+
const locator = page.locator('button[name="pizza-please"]');
132+
133+
await locator.click();
134+
```
135+
136+
API calls that interact with Chromium are asynchronous and require `await` to ensure completion before proceeding. Synchronous APIs, such as `page.locator`, do not require `await`, but using it does not cause issues since the JavaScript runtime will simply return the value immediately.
137+
138+
If you don't add `await` on asynchronous APIs, it can cause the script to finish before the test completes, resulting in errors like `"Uncaught (in promise) TypeError: Object has no member 'goto'"`. That can happen because the page object from `browser.newPage()` without an `await` is actually a JavaScript promise. You can try and see the error using the following code snippet:
139+
140+
<!-- eslint-skip -->
141+
142+
```js
143+
const page = browser.newPage();
144+
145+
page.goto('https://quickpizza.grafana.com/'); // An error should occur since we're not using await in the line above.
146+
147+
const locator = page.locator('button[name="pizza-please"]');
148+
149+
locator.click();
150+
```

0 commit comments

Comments
 (0)