Important Note

Speedway has moved into Loadster Site & API Monitoring.

If you have an existing Speedway account, you can use the same credentials to sign in to Loadster. Don't worry, your monitors haven't missed a beat and your monitoring data is intact. Everything that Speedway did, Loadster now does the same or better.

We're leaving the Speedway documentation up for the time being, but strongly encourage you to refer to the Site & API Monitoring section of the Loadster manual.

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 Speedway 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.