User Journeys
Run a real browser through your site — log in, search, check out — on a schedule, and find out which step broke when it stops working.
How a journey check works
A User journey monitor runs a Playwright script in a headless browser on Zharp's servers. You can record the script by using your site in the browser recorder, or write it yourself. On every check, each selected region opens a fresh browser, runs the script from the top, and closes the browser again.
The run passes when the script finishes. It fails when:
- a step throws — an element that never appears, a click that can't land, a page that won't load;
- an
expectcheck doesn't hold; - the whole script runs longer than Max duration (60 seconds unless you change it).
Each region's result is combined into one status for the monitor, as described in Core Concepts. The journey starts at the URL of the script's first page.goto(...); a script without one can't be saved.
Create a user journey monitor
- 1Choose the type
On the Monitors page, click New, open the type picker and choose User journey monitor. It is listed under Application & APIs.
- 2Build the journey
Under How do you want to build this journey?, click Open browser recorder to record it (see below), or click or edit the Playwright script manually to write it in the script editor. The link under either one switches to the other: or use the browser recorder instead.
- 3Choose the browser
Open Advanced settings. Browser settings has the browser, viewport, time limit and screenshot options in the table below.
- 4Create it
Pick who is alerted under How should we escalate incidents? and click Create monitor. If you leave the name empty, the monitor is named after the journey's starting URL.
Check frequency, Regions and the other settings every monitor has are described in Monitor Types. Every selected region runs the whole journey on every check, so a long journey with four regions every 30 seconds keeps a lot of browsers busy on your site — a few minutes is usually enough.
Record a journey
The recorder opens a real browser on Zharp's servers and streams it to you. What you do in it becomes a Playwright script.
- 1Open the recorder
Click Open browser recorder. Type the starting URL in the address bar —
https://is added if you leave it off — and press Open. - 2Get to the starting point
The page loads at 1280×800 and you see it live, with back, forward and reload buttons beside the address bar. Nothing is captured yet, so you can click around, dismiss a cookie banner, or find the page the journey should start from.
- 3Record
Press Record and use the page. Clicks, typing, key presses and navigations become steps in the Recording list beside the page. Pauses aren't recorded: each step waits for the page instead — an action for its element, a check until it passes — so a run takes as long as the page does, not as long as you did. Add a wait step in the list if a page really needs one.
- 4Add checks
Switch from Interact to Assert and click an element on the page. That adds a step that checks the element is visible — use it on whatever proves the journey worked, such as the heading of the page you land on after logging in. Switch back to Interact to carry on.
- 5Stop
Press Stop. The recorder shows how many steps were recorded and says The Playwright script has been generated and saved. — it is now the monitor's script. Click Replay to watch the steps run again, Re-record to throw them away and start over, or Done to close the recorder.
Back in the form, the recorded steps are listed under N steps recorded, with Re-record and Replay. Reopening the recorder with steps already saved shows Continue in place of Record, which adds to the journey rather than replacing it. In the recorder, remove a step with its delete button. Once a recording is stopped or replayed, each step also has an edit button: change the element selector of a click or visibility check, or the value of any other step — the text typed, the URL, the key, the text to find or the wait — and press Enter. The script is generated again with the change; Escape leaves the step as it was.
Fragile selectors
The recorder picks a selector for every element you touch. When the best it can find is likely to change — a position such as :nth-child, a bare tag name, a plain chain of tags, or visible text containing numbers — the step gets a warning icon (This selector may be unstable) and the generated script carries a comment above it. Those steps are the ones that break when the page changes. Give the element a steady attribute such as data-testid or name, then edit the script to use it.
The generated script checks each element is visible before clicking or typing into it, checks a field holds what was typed into it, and turns a pause into page.waitForTimeout(...). Open it with or edit the Playwright script manually to tidy it up.
Write the script
The script editor starts with this example. Replace the URL, selectors and checks with your own. The journey must contain a page.goto(...) with the URL written out as a string: that URL is where the journey starts.
import { test, expect } from '@playwright/test';
test('user journey', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'secret');
await page.click('[type="submit"]');
await expect(page).toHaveURL(/dashboard/);
await expect(page.locator('h1')).toContainText('Welcome');
});What a script can use
importlines are removed before the script runs. The script getstest,expect,page,context,browserandconsole. Thetestcallback receivespage,context,browserandrequest, for calling an API alongside the page.- The usual
pageactions work:goto,click,fill,type,press,check,selectOption,hover,waitForSelector,waitForURL, locators (page.locator(...),page.getByRole(...)and the othergetBymethods),page.keyboardand most of the rest of the Playwright page API. - Page checks:
toHaveURLandtoHaveTitle. Locator checks:toBeVisible,toBeHidden,toContainText,toHaveText,toHaveValue,toHaveAttribute,toHaveCount,toBeEnabled,toBeDisabled,toBeEditable,toBeCheckedandtoBeAttached. Value checks:toBe,toEqual,toContain,toMatch,toBeTruthy,toBeFalsy,toBeGreaterThan,toBeLessThanandtoBeOKfor a response. Every check can be turned around with.not, as inexpect(page.locator('.error')).not.toBeVisible(). console.log,console.warnandconsole.errorlines appear in the editor's output when you run the script.
timeout, as in await expect(page).toHaveURL(/dashboard/, { timeout: 15000 }). The wait never runs past Max duration. Value checks such as toBe look once.Name the steps
The monitor page lists every action — on the page, on a locator or with the keyboard — and every expect check the run made, in order — Load /login, Fill field, Click, Verify URL and so on. Wrap groups of them in test.step and the page lists your named steps instead:
test('checkout', async ({ page }) => {
await test.step('Open the shop', async () => {
await page.goto('https://shop.example.com');
await expect(page).toHaveTitle(/Shop/);
});
await test.step('Add to cart', async () => {
await page.click('[data-test="add-to-cart"]');
await page.waitForSelector('[data-test="cart-count"]');
await expect(page.locator('[data-test="cart-count"]')).toContainText('1');
});
});What a script can't do
The script runs in an isolated sandbox with 128 MB of memory. It has no access to Node.js modules, files, or the server's environment. Pages open over http and https only, and any request — navigation, redirect, image, script or API call — to a private or internal address is blocked, so a journey can't reach localhost or your internal network. File uploads from a path on disk aren't available.
Test the script
In the script editor, click Run Script. The journey runs once on Zharp's servers and the Terminal panel opens with its output: the pages it loaded, each test and test.step as it passes or fails, your console lines, any request that was blocked, and the error a failed run stopped on. Nothing is saved by a test run.
The test run uses the form's Browser settings — browser, viewport, max duration and screenshots — so it runs the way the scheduled checks will. When a run fails with Screenshots on On failure or Always, the Screenshot tab beside Output shows the page as it stood when it failed.
Browser settings
Under Advanced settings, Browser settings decide what the journey runs in:
| Setting | Default | What it does |
|---|---|---|
| Browser | Chromium | Chromium, Google Chrome, Mozilla Firefox or WebKit (Safari). Google Chrome runs the branded Chrome build where it is installed on the checking server, and Chromium otherwise — the run's log says so, and the browser shown for the run is the one that actually ran. |
| Viewport | Desktop (1280×800) | The window size: Desktop (1280×800), Full HD (1920×1080), Mobile (375×812) or Tablet (768×1024). Only the size changes — the browser still identifies itself the same way, so a site that serves mobile pages by user agent shows its responsive layout, not its mobile site. |
| Max duration | 60 seconds | 30, 60, 120 or 300 seconds for the whole script. A run still going at the limit is stopped and fails. Waits count toward it. |
| Screenshots | On failure | On failure: when a run fails, the page is captured as it stood at the failure. Always: the page is also captured at the end of every passing run. Never: no capture when a run fails. |
Every run identifies itself with the User-Agent Zharp/1.0 (Synthetic Monitor).
When it goes down
A failed run records a message that starts with Synthetic Check Failed: followed by the error. Expand the run under Recent runs to read it; the incident words the cause in plain language.
| What happened | Message on the run |
|---|---|
| The script ran past Max duration | Synthetic Check Failed: Script execution timed out after 60000ms |
| A toHaveURL check didn't match | Synthetic Check Failed: Expected URL /dashboard/, got https://example.com/login |
| A toContainText check didn't match | Synthetic Check Failed: Expected text "Welcome", got "Sign in" |
| A text check's element never appeared | Synthetic Check Failed: Expected text "Welcome", but the element was not found |
| A toBeVisible check timed out | Synthetic Check Failed: Expected locator('#results') to be visible |
| A toHaveValue check didn't match | Synthetic Check Failed: Expected locator('#email') to have value "a@b.com", got "" |
| A toHaveTitle check didn't match | Synthetic Check Failed: Expected title Shop, got Not found |
| A Playwright action failed, for example an element that never appeared | The Playwright error, for example Synthetic Check Failed: page.click: Timeout 30000ms exceeded. |
| The script went to a non-http(s) URL | Synthetic Check Failed: Navigating to file: URLs is not allowed in scripts — only http(s) pages can be opened |
| The script used more than 128 MB | Synthetic Check Failed: Script ran out of memory (limit exceeded) and was stopped |
With Screenshots on On failure or Always, the picture of the failing page goes with the incident and appears on its timeline. Zharp keeps the journey's latest final screenshot for each state (up and down); of the pictures taken along the way — one for each page the journey loads — it keeps only where and when each was taken, not the images.
The monitor page
A user journey monitor's page shows:
- The journey's name, its number of steps and the browser, with PASSED or FAILED for the last run, and a Journey duration chart.
- LAST RUN — steps passed out of the total, Total time, Browser, Viewport, Success rate over the runs shown, and Last run.
- Journey steps — each step of the last run with how long it took, and a cross on the one that failed.
- Recent runs — one row per run with PASS or FAIL, Steps passed, Duration, Failed step and Location. Expand a row for the browser, viewport and region it ran in and, for a failure, the step, the action it was running and the error.
- The Journey card with the settings in force: steps, browser, viewport, device, timeout and screenshots.
Troubleshooting
A recorded journey fails on replay or on schedule
- Look at the failing step in Recent runs. A step marked with the unstable-selector warning is the usual cause: give the element a steady attribute and update the selector in the script.
- Checks wait up to 5 seconds for an element. One that takes longer to load needs a longer check, such as
await expect(page.locator('#results')).toBeVisible({ timeout: 20000 }). - Recorded values are replayed exactly. A one-time code, a date or anything else that changes between runs needs editing in the script.
It passes in the editor but fails as a monitor
A test run uses the same browser settings, but runs once, from one place. Check whether only some regions fail — the Location column in Recent runs shows which — and whether the site is slower or behaves differently from there, or at the times the checks run.
The journey is blocked by the site
Bot protection and firewalls can stop the browser. Allow requests whose User-Agent is Zharp/1.0 (Synthetic Monitor). A journey can't reach a site on a private or internal address.
The monitor can't be saved
The form asks for The journey needs a page.goto(...) so we know where to start when the script has no page.goto with a written-out URL. Add one as the first step, for example await page.goto('https://example.com').