Documentation for automated readers
A curated documentation index is available at: https://grafana.com/llms.txt
A complete documentation index is available at: https://grafana.com/llms-full.txt
These indexes can help with page discovery before fetching individual documents.
This page is also available in Markdown, which may be easier for automated readers and AI tools to parse than HTML. The Markdown version is available at https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/create-checks/write-resilient-check-scripts.md, or by sending Accept: text/markdown to https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/create-checks/write-resilient-check-scripts/. For broader documentation discovery, the curated index is available at https://grafana.com/llms.txt and the complete index is available at https://grafana.com/llms-full.txt.
Write resilient check scripts
A check should fail when the outcome you monitor fails. Fixed delays, selectors tied to page styling, and requests that continue after a failed prerequisite can make a check fail for a different reason or obscure the original failure.
Use these patterns when writing k6 browser checks and k6 scripted checks. The browser examples are fragments to use inside an asynchronous test function with an existing page. The HTTP examples use an existing response and a baseUrl for your application.
Wait for the expected content
A fixed delay assumes an action completes within a particular time. It waits unnecessarily when the application is fast and can read the page too early when the application is slower.
For example, this code waits five seconds regardless of whether the ratings list has finished updating:
await page.waitForTimeout(5000);
const listItem = await page.locator('li').textContent();Instead, wait for the application data to finish loading before checking the expected content. QuickPizza displays No ratings yet while its ratings request is pending, so that text alone doesn’t prove the list is empty.
The following fragment uses an existing page and the URL of the ratings page, ratingsUrl. Import fail from k6 at the top of your script.
const ratingsPromise = page.waitForResponse(/\/api\/ratings$/);
await page.goto(ratingsUrl);
const response = await ratingsPromise;
if (response.status() !== 200 || (await response.json()).ratings.length !== 0) {
fail('Expected an empty ratings list');
}
await page.waitForFunction(() => document.querySelector('li')?.textContent.trim() === 'No ratings yet');
const listItem = await page.locator('li').textContent();Register the response wait before triggering the request. For actions that delete data and reload the page, also distinguish the deletion response from the refreshed data request. The browser check tutorial shows the complete sequence. Adapt the request URL, response validation, selector, and expected content to your application.
page.waitForFunction() returns when the condition becomes true or throws an error when its timeout expires. To wait for an element to become visible, hidden, attached, or detached, use locator.waitFor().
Use a timeout that leaves enough time for the rest of the journey within the check’s overall timeout. A timeout is an upper bound on waiting. It doesn’t require the check to pause for that entire duration. Avoid disabling timeouts to make a failing wait pass.
Avoid using networkidle as a readiness condition. Background requests can prevent it from occurring even when the application is ready. For details, refer to page.waitForLoadState().
Select elements by their purpose
A selector based on a styling class can stop matching after a visual change, even when the user journey still works:
await page.locator('button.w-32').click();Prefer an element’s role and accessible name, or an attribute your application maintains for testing. For a button labeled Clear Ratings, use:
await page.getByRole('button', { name: 'Clear Ratings', exact: true }).click();The selector describes the intended action and survives changes to the button’s width or color. Keep it specific enough to identify one element. If several buttons have the same name, scope the locator to the relevant part of the page rather than relying on their order.
For supported role and name options, refer to page.getByRole().
Validate a response before using its data
A later request often depends on a value returned by an earlier request. Reading that value without first validating the response can produce a misleading error. For example, this code assumes a create-rating response contains a valid ID:
const id = response.json('id');
http.del(`${baseUrl}/api/ratings/${id}`);Validate the status, parse the body, and validate the required value before continuing. The following fragment assumes response is the result of a request to create a rating:
import http from 'k6/http';
import { check, fail } from 'k6';
// Inside the test function, after the create-rating request:
if (!check(response, { 'rating created': (r) => r.status === 201 })) {
fail(`Create rating returned status ${response.status}`);
}
let id;
try {
id = response.json()?.id;
} catch (error) {
fail('Create rating returned invalid JSON');
}
if (!check(id, { 'rating has a valid ID': (value) => Number.isInteger(value) && value > 0 })) {
fail('Create rating returned no valid ID');
}
const deletion = http.del(`${baseUrl}/api/ratings/${id}`);
if (!check(deletion, { 'rating deleted': (r) => r.status === 204 })) {
fail(`Delete rating returned status ${deletion.status}`);
}Place the imports at the top of your script and the remaining code inside your test function. Adapt the status codes and body validation to your API contract.
check() records the validation result and returns a boolean. A failed check doesn’t stop the script by itself. fail() throws an error and stops the current iteration, so dependent requests don’t run after the prerequisite fails.
When using the asynchronous check helper from k6-utils with browser code, await its result before deciding whether to continue, as shown in the browser check tutorial.
Preserve failure details and release browser resources
Give each validation a name that identifies the step and expected outcome, such as rating created or rating deleted. This helps you distinguish the first failure from errors in later steps.
Use try and finally to close a browser page even when a wait, action, or validation throws an error:
const page = await browser.newPage();
try {
// Run the journey and validate its outcomes.
} finally {
await page.close();
}If you catch an error to add context, propagate the failure instead of logging it and continuing as though the step succeeded. For how to inspect results from individual probes, refer to Visualize check execution.
Was this page helpful?
Related resources from Grafana Labs


