Evaluate Blocks
Normally, Browser Bots control their browsers with simple user actions, like navigate, click, and type. For many sites that’s all you need to build a realistic load test script.
But what about more complicated sites with features like drag-and-drop, range sliders, custom drawing, and background recalculations?
For situations like this, you might need to call JavaScript functions on the page directly, or extract values from within the page’s DOM.
Evaluate Blocks, or eval blocks, let you execute your own JavaScript code inside the browser. This is a powerful way to do special on-page automation more precisely than you can with simple actions.
Extracting a Simple Value
The simplest eval blocks grab a single value from the page. You can eval 1 + 1
to get 2
, or you can eval
navigator.language
to get the language preference of the browser, or new Date()
to get the current date.
The last (or only) statement in your evaluate block is what gets returned.
Extracting a simple value like this is easy, but not particularly useful except for debugging your script.
Calling a JavaScript Function on the Page
From an eval block you can call any function that is declared in the page, much like you could in your browser’s JavaScript console.
This might be something simple like console.log()
to print a message to the JavaScript console, which you
can then see in your script logs in single-user mode. That’s a nice trick for checking what’s happening on the page.
You could also use an eval block to call one of your own functions that’s declared on the page.
recalculateShippingAmounts({
product: "product-38a64b",
quantity: 1,
destination: 92091
});
Most of the time, calling page functions directly isn’t necessary, since you can interact with the site using normal user inputs (navigating, clicking on elements, entering text into fields, and so on). But for some sites, evaluating code inside the browser is essential.
Scrolling the Page
Normally when your script tries to interact with an element, the bot will automatically scroll the browser if needed to reveal the element, as long as the element is visible and exists in the DOM. The only times you should need to explicitly scroll in your script is if the element is created upon scrolling, like with those “infinite scroll” feeds or “parallax” sites.
If you need to manually scroll the page, you can do this in an evaluate block using the standard JavaScript
scroll methods. The same approach should work with
scroll
, scrollBy
, scrollIntoView
, etc.
// Scroll the document to specific page coordinates
window.scroll(115, 1265);
// Smooth the document down 100 pixels
window.scrollBy(0, 100);
// Smooth scroll to bring an element into view
document.querySelector("#loadmore").scrollIntoView({ behavior: "smooth" });
There is nothing unique about our implementation of browser scrolling, it’s just standard JavaScript that you run in an evaluate block.
Evaluating a JavaScript Promise
If your eval block returns a promise, the bot will automatically wait for the promise to resolve. This means you can do asynchronous programming and long-running operations within the eval block.
For example, here is an eval block that calls a function on the page to refresh the products list, and then checks a few seconds later to make sure at least three products have loaded.
new Promise((resolve, reject) => {
refreshProductsList();
setTimeout(() => {
if (document.querySelectorAll('.product').length >= 3) {
resolve('all products loaded');
} else {
reject('products failed to asynchronously load in time!');
}
}, 5000);
});
If the products loaded, the promise is resolved. If they failed to load, the promise is rejected, raising a script error.
Throwing an Error for Custom Validation
If your eval block throws an error of any kind, the bot will catch the error and raise it as a script error. This is a nice way to add custom validation to your browser scripts, similar to how protocol scripts have validation rules.
For example, your eval block could throw an error if any element matching the selector .error-msg
exists on the page.
var errors = document.querySelectorAll('.error-msg');
if (errors.length > 0) {
throw new Error("The page is showing an error: " + errors[0].innerText);
}
Similarly, you can throw an error if something you expect is not found on the page.
if (!document.getElementById('login-success')) {
throw new Error("Login failed!");
}
When you’re testing a complicated site, adding manual validation with eval blocks is quite helpful, since errors could otherwise go undetected.
Communicating via WebSockets
You can test WebSockets in an eval block. Keep in mind that eval blocks run inside the bot’s browser on a page, so it has to be done after your script has already loaded a page into the browser with a Navigate step.
Here’s a simple example of an eval block that runs on the page. It opens a socket, sends three ping messages, receives three pong responses, and then resolves the promise after the third pong has been received.
// Sends 3 pings, gets 3 pongs, and resolves the promise
new Promise((resolve, reject) => {
let pongs = 0;
let socket = new WebSocket("wss://echo.websocket.org");
socket.onopen = function(e) {
ping();
};
socket.onmessage = function(event) {
pongs++;
if (pongs === 3) {
socket.close();
resolve('got 3 pongs!');
}
};
function ping() {
if (!socket || socket.readyState !== 1) return;
socket.send("ping");
setTimeout(ping, 500);
}
});
Using a promise here is critical since the socket operations happen asynchronously and could take a while. Wrapping
it in a Promise
makes your script wait until the promise is resolved before moving on. Otherwise, the
bot would quickly move on without waiting for these asynchronous operations to finish.
If the promise is never resolved or rejected it will eventually time out with an error, according to your timeout configuration in Settings.
Making Sure a Video is Playing
Some sites have video players, and you might have a scripting requirement to make sure the video is actually loading and playing for your bots.
Here’s a user-contributed example (thanks Phil!) of making sure the video loads and plays with Mux, one of the popular
video players. Other players probably have similar events to timeupdate
that you can listen for in your promise.
// Listen for 'timeupdate' events and resolve after 1 second of video
new Promise((resolve, reject) => {
const player = document.querySelector('mux-player');
if (player) {
player.addEventListener('timeupdate', () => {
if (player.currentTime > 1.00) {
resolve(player.currentTime);
}
});
} else {
reject('Mux Player not found!');
}
});
When you run this in a Loadster script, it will throw an error immediately if the player isn’t found at all, but otherwise pauses until the video timestamp passes 1 second. If your script makes it through this block with no error, it means the video played and the promise was resolved.