Skip to main content

BigCommerce uptime monitoring with Checklyhq.com synthetic monitoring

Posted by Cameron Murphy on 9th Dec 2022

BigCommerce (my ecommerce backend provider) has been going through a week of server / database problems that kicked off just after Black Friday 2022.  This post is my attempt to document, and hopefully help other BigCommerce or other Ecommerce site owners get a handle on monitoring their site's operational status.  

The frustrating part for me, and a number of other BigCommerce users is that our sites *appear* to be working properly.  The homepage, and many product pages load just fine.  It isn't until a customer tries to add an item to the cart, or checkout that the wheels fall off the bus.    I've experienced several hours long periods where my site has been non-functional, despite still passing uptime checks from uptimerobot.com (which looks for an HTML keyword to prove the site "loaded" properly.)

What I need, is an automated tool that goes through the motions of an actual customer, and tries to use my site to make a purchase.   Luckily, these tools exist -- I just wasn't aware of them until this current mess.  Apparently, this field is called "Synthetic Transaction Monitoring"  There are a whole range of options, from open source "roll your own", to very expensive "track everything and the kitchen sink" solutions.

I chose to use a tool called "Checkly" at checklyhq.com   They provide a SaaS solution that provides a web based dashboard and alerting tools to wrap automated tests run using either Puppeteer or Playwright to script interactions using chrome browsers.  This allows for tests that simulate real user interactions, including javascript.   They offer a free plan that currently allows for 5 browser based checks, and up to 50,000 checks per month.  Paid plans start at $70/month for significantly expanded usage.

Checkly has quite a few guides on their site, and I was able to get up and running using this one: End to End Monitoring Setup  They also have browser check quickstart here.

Using the scripting tool Playwright, I came up with the following script:  (It searches for an item, adds it to the cart, and goes through 95% of the checkout before emptying the cart.)

/** * This is a basic Playwright script to get you started! * To learn more about Browser checks and Playwright visit: <a href="https://www.checklyhq.com/docs/browser-checks">https://www.checklyhq.com/docs/browser-checks</a> *

/ Create a Chromium browser const { chromium } = require('playwright')

// Checkly supports top level await, but we wrap your code in an async function so you can run it locally too. async function run () { const browser = await chromium.launch() const page = await browser.newPage()

// We visit the page. This waits for the "load" event by default. const response = await page.goto('YOUR_URL')

// If the page doesn't return a successful response code, we fail the check. if (response.status() > 399) { throw new Error(`Failed with response code ${response.status()}`) }

await page.getByRole('button', { name: 'Search' }).click();

await page.getByRole('textbox', { name: 'Search' }).fill('ITEM_NAME');

await page.getByRole('textbox', { name: 'Search' }).press('Enter');

await page.locator('[data-test="card-828"]').getByRole('link', { name: 'Choose Options' }).click();

await page.locator('label').filter({ hasText: 'OPTION1_TEXT' }).click();

await page.locator('label').filter({ hasText: 'OPTION2_TEXT' }).click();

await page.locator('div:nth-child(3) > div:nth-child(4) > .form-option > .form-option-variant').click();

await page.getByRole('button', { name: 'Add to Cart' }).click();

await page.getByRole('link', { name: 'Proceed to checkout' }).click();

await page.getByLabel('Email').click();

await page.getByLabel('Email').fill('TEST_EMAIL');

await page.locator('[data-test="customer-continue-as-guest-button"]').click();

await page.locator('[data-test="firstNameInput-text"]').fill('Joe');

await page.locator('[data-test="lastNameInput-text"]').fill('User');

await page.locator('[data-test="phoneInput-text"]').fill('111-222-3333');

await page.locator('[data-test="addressLine1Input-text"]').fill('123 Somewhere St.'); await page.locator('[data-test="cityInput-text"]').fill('Springfield');

await page.locator('[data-test="provinceCodeInput-select"]').selectOption('MO');

await page.locator('[data-test="postCodeInput-text"]').fill('12345');

await page.getByText('My billing address is the same as my shipping address.').click();

await page.getByText('USPS Priority Mail (2-3 Days)').click();

await page.locator('[data-test="checkout-shipping-comments"]').getByRole('textbox').fill('Test Order');

await page.getByRole('button', { name: 'Continue' }).click();

await page.locator('[data-test="firstNameInput-text"] >> visible=true').fill('Another');

await page.locator('[data-test="lastNameInput-text"] >> visible=true').fill('User'); await page.locator('[data-test="phoneInput-text"] >> visible=true').fill('444-555-6666');

await page.locator('[data-test="addressLine1Input-text"] >> visible=true').fill('456 Another St.');

await page.locator('[data-test="cityInput-text"] >> visible=true').fill('Malibu'); await page.locator('[data-test="provinceCodeInput-select"] >> visible=true').selectOption('CA');

await page.locator('[data-test="postCodeInput-text"] >> visible=true').fill('56789'); await page.getByRole('button', { name: 'Continue' }).click();

await page.locator('[data-test="cart-edit-link"]').click();

await page.getByRole('button', { name: 'Remove Barnard Model P from cart' }).click(); await page.getByRole('button', { name: 'OK' }).click();

await page.getByText('Home Your Cart Your Cart (0 items) Your cart is empty').click();

// We snap a screenshot. await page.screenshot({ path: 'screenshot.jpg' })

// We close the page to clean up and gather performance metrics. await page.close() await browser.close() }

run()

This should give you a starting point to adapt to your site.  I've replaced labels specific to my my site with "OPTION1_TEXT", etc.   

One last point, using the one page checkout, the shipping and billing info reuse the same input names, with one or the other hidden as the form progresses.  The default recording doesn't take this into account, and only writes to the shipping info, even after the billing info is made visible.  I had to add the the additional ">> visible=true" selector after the text name for the billing fields to ensure it was only writing to the now visible fields with those names.

I've got this setup to run every 15 minutes, from servers in CA and VA.  This *should* alert me to any future breakdowns in the BigCommerce backend API servers.

Note: This will screw up your BigCommerce Analytics.  It doesn't appear to be possible to exclude or ignore ip addresses from their built in analytics.  It will add lots of abandoned carts, and deleting the item at the end of the test doesn't seem to prevent them counting it as an abandoned cart.

Google Analytics on the other hand, does allow you to exclude data, and it would be wise to whitelist the ips from any servers you choose to test from.