Run end-to-end tests
Run browser tests against Browserless without installing or managing Chrome locally.
- A Browserless API token from your account dashboard
Steps
- AI Agent
- REST API
- Frameworks
- BQL
Use the Browserless MCP server to run end-to-end tests from any MCP-compatible AI agent (Claude Desktop, Cursor, Windsurf, ChatGPT, etc.).
1. Connect the MCP server
Send this prompt to your AI agent to install the Browserless MCP server:
Go to https://github.com/browserless/browserless-mcp/blob/main/install.md
and follow the instructions to install the Browserless MCP server
for my client.
2. Run an E2E test
Use browserless_agent. E2E testing requires multi-step interaction with the page.
Use the browserless_agent tool to go to https://scraping-sandbox.netlify.app/products,
verify the page loads correctly, check the product catalog heading
is visible, click on a product, and confirm the product details appear
The /function endpoint lets you send a browser script as the request body and get structured JSON back. It's the fastest way to drop a smoke test into a CI pipeline without adding a framework dependency. You're just sending HTTP and checking a passed boolean.
- cURL
- JavaScript
- Python
- Java
- C#
1. Write the test script
The /function endpoint accepts a JavaScript module as the request body. The exported function receives a page object and can return any JSON-serializable value:
2. Send the request
curl -X POST \
"https://production-sfo.browserless.io/function?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/javascript" \
--data-raw 'module.exports = async ({ page }) => {
await page.goto("https://scraping-sandbox.netlify.app/products");
const title = await page.title();
const heading = await page.$eval("h1", el => el.textContent.trim());
const hasLink = (await page.$("a[href]")) !== null;
const results = [
{ test: "title includes Product Catalog", passed: title.includes("Product Catalog") },
{ test: "h1 reads Product Catalog", passed: heading === "Product Catalog" },
{ test: "page has at least one link", passed: hasLink },
];
return { passed: results.every(r => r.passed), results };
};'
3. Check the output
{
"passed": true,
"results": [
{ "test": "title includes Product Catalog", "passed": true },
{ "test": "h1 reads Product Catalog", "passed": true },
{ "test": "page has at least one link", "passed": true }
]
}
1. Send the request
const code = `module.exports = async ({ page }) => {
await page.goto('https://scraping-sandbox.netlify.app/products');
const title = await page.title();
const heading = await page.$eval('h1', el => el.textContent.trim());
const hasLink = (await page.$('a[href]')) !== null;
const results = [
{ test: 'title includes Product Catalog', passed: title.includes('Product Catalog') },
{ test: 'h1 reads Product Catalog', passed: heading === 'Product Catalog' },
{ test: 'page has at least one link', passed: hasLink },
];
return { passed: results.every(r => r.passed), results };
};`;
const response = await fetch(
'https://production-sfo.browserless.io/function?token=YOUR_API_TOKEN_HERE',
{
method: 'POST',
headers: { 'Content-Type': 'application/javascript' },
body: code,
}
);
const { passed, results } = await response.json();
results.forEach(r => console.log(`${r.passed ? '✓' : '✗'} ${r.test}`));
if (!passed) process.exit(1);
2. Check the output
Run with node e2e-testing.mjs. Each test prints a pass or fail line. The process exits with code 1 if any test fails, so CI systems pick up the failure automatically.
1. Install dependencies
pip install requests
2. Send the request
import sys
import requests
code = """
module.exports = async ({ page }) => {
await page.goto('https://scraping-sandbox.netlify.app/products');
const title = await page.title();
const heading = await page.$eval('h1', el => el.textContent.trim());
const hasLink = (await page.$('a[href]')) !== null;
const results = [
{ test: 'title includes Product Catalog', passed: title.includes('Product Catalog') },
{ test: 'h1 reads Product Catalog', passed: heading === 'Product Catalog' },
{ test: 'page has at least one link', passed: hasLink },
];
return { passed: results.every(r => r.passed), results };
};
"""
response = requests.post(
'https://production-sfo.browserless.io/function?token=YOUR_API_TOKEN_HERE',
headers={'Content-Type': 'application/javascript'},
data=code.encode('utf-8'),
)
data = response.json()
for result in data['results']:
icon = '✓' if result['passed'] else '✗'
print(f"{icon} {result['test']}")
if not data['passed']:
sys.exit(1)
3. Check the output
Run with python e2e_testing.py. The process exits with code 1 if any test fails.
1. Send the request
import java.net.URI;
import java.net.http.*;
String token = "YOUR_API_TOKEN_HERE";
String endpoint = "https://production-sfo.browserless.io/function?token=" + token;
String code = """
module.exports = async ({ page }) => {
await page.goto('https://scraping-sandbox.netlify.app/products');
const title = await page.title();
const heading = await page.$eval('h1', el => el.textContent.trim());
const hasLink = (await page.$('a[href]')) !== null;
const results = [
{ test: 'title includes Product Catalog', passed: title.includes('Product Catalog') },
{ test: 'h1 reads Product Catalog', passed: heading === 'Product Catalog' },
{ test: 'page has at least one link', passed: hasLink },
];
return { passed: results.every(r => r.passed), results };
};
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/javascript")
.POST(HttpRequest.BodyPublishers.ofString(code))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
2. Check the output
Run the class. The response body is JSON with a passed boolean and a results array containing one entry per assertion.
1. Send the request
using System.Net.Http;
using System.Text;
string token = "YOUR_API_TOKEN_HERE";
string endpoint = $"https://production-sfo.browserless.io/function?token={token}";
string code = @"module.exports = async ({ page }) => {
await page.goto('https://scraping-sandbox.netlify.app/products');
const title = await page.title();
const heading = await page.$eval('h1', el => el.textContent.trim());
const hasLink = (await page.$('a[href]')) !== null;
const results = [
{ test: 'title includes Product Catalog', passed: title.includes('Product Catalog') },
{ test: 'h1 reads Product Catalog', passed: heading === 'Product Catalog' },
{ test: 'page has at least one link', passed: hasLink },
];
return { passed: results.every(r => r.passed), results };
};";
using (HttpClient httpClient = new HttpClient())
{
var content = new StringContent(code, Encoding.UTF8, "application/javascript");
var response = await httpClient.PostAsync(endpoint, content);
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
2. Check the output
Run the program. The response body is JSON with a passed boolean and a results array containing one entry per assertion.
Use a browser framework when you need a full assertion library, multi-step flows, or parallel test execution. Frameworks give you retrying assertions, test lifecycle hooks, and reporters that the raw REST API doesn't provide.
- Puppeteer
- Playwright (JS)
- Playwright (Python)
- Playwright (Java)
- Playwright (C#)
1. Install dependencies
npm install puppeteer-core
2. Connect and run assertions
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE',
});
const results = [];
try {
const page = await browser.newPage();
await page.goto('https://scraping-sandbox.netlify.app/products', { waitUntil: 'networkidle2' });
const title = await page.title();
results.push({
test: 'title includes "Product Catalog"',
passed: title.includes('Product Catalog'),
});
const heading = await page.$eval('h1', el => el.textContent.trim());
results.push({
test: 'h1 reads "Product Catalog"',
passed: heading === 'Product Catalog',
});
const link = await page.$('a[href]');
results.push({
test: 'page has at least one link',
passed: link !== null,
});
} finally {
await browser.close();
}
const failed = results.filter(r => !r.passed);
results.forEach(r => console.log(`${r.passed ? '✓' : '✗'} ${r.test}`));
if (failed.length) {
console.error(`\n${failed.length} test(s) failed`);
process.exit(1);
}
3. Check the output
Run with node e2e-testing.mjs. The process exits with code 1 if any assertion fails, so your CI system registers it as a failure.
✓ title includes "Product Catalog"
✓ h1 reads "Product Catalog"
✓ page has at least one link
1. Install dependencies
npm install @playwright/test playwright-core
2. Configure Browserless
Add a playwright.config.js file. The wsEndpoint uses /chromium/playwright rather than a plain CDP endpoint because Playwright's connection protocol requires the dedicated Playwright path. Plain CDP won't negotiate the right capabilities. Set workers to control parallelism, retries: 1 so flaky tests get one retry, and trace: 'on-first-retry' so Playwright captures a trace zip only on that retry. Skipping it on the first pass keeps things fast, and you only need it when something actually fails:
import { defineConfig } from '@playwright/test';
export default defineConfig({
workers: 4,
retries: 1,
use: {
trace: 'on-first-retry',
connectOptions: {
wsEndpoint: 'wss://production-sfo.browserless.io/chromium/playwright?token=YOUR_API_TOKEN_HERE',
},
},
});
3. Write tests
Playwright's expect() assertions are web-first. They poll the DOM until the condition passes or the timeout expires, so you don't need manual waits. Use page.route() inside individual tests to intercept or mock specific network requests without affecting other tests:
// tests/example.spec.js
import { test, expect } from '@playwright/test';
test('page has correct title', async ({ page }) => {
await page.goto('https://scraping-sandbox.netlify.app/products');
await expect(page).toHaveTitle(/Product Catalog/);
});
test('heading is visible and correct', async ({ page }) => {
await page.goto('https://scraping-sandbox.netlify.app/products');
const heading = page.locator('h1');
await expect(heading).toBeVisible();
await expect(heading).toHaveText('Product Catalog');
});
test('page contains a link', async ({ page }) => {
await page.goto('https://scraping-sandbox.netlify.app/products');
await expect(page.locator('a[href]').first()).toBeVisible();
});
test('page loads without analytics calls', async ({ page }) => {
const blocked = [];
await page.route('**/analytics/**', route => {
blocked.push(route.request().url());
route.abort();
});
await page.goto('https://scraping-sandbox.netlify.app/products');
await expect(page).toHaveTitle(/Product Catalog/);
console.log(`Blocked ${blocked.length} analytics request(s)`);
});
4. Run the tests
npx playwright test
5. Cross-browser testing
Browserless exposes separate endpoints for Chromium, Firefox, and WebKit. Use Playwright's projects array to target all three from the same config. Each project connects to its own endpoint and Playwright runs them as distinct test suites:
import { defineConfig } from '@playwright/test';
const token = 'YOUR_API_TOKEN_HERE';
export default defineConfig({
workers: 4,
retries: 1,
use: { trace: 'on-first-retry' },
projects: [
{
name: 'chromium',
use: {
connectOptions: {
wsEndpoint: `wss://production-sfo.browserless.io/chromium/playwright?token=${token}`,
},
},
},
{
name: 'firefox',
use: {
connectOptions: {
wsEndpoint: `wss://production-sfo.browserless.io/firefox/playwright?token=${token}`,
},
},
},
{
name: 'webkit',
use: {
connectOptions: {
wsEndpoint: `wss://production-sfo.browserless.io/webkit/playwright?token=${token}`,
},
},
},
],
});
Run with npx playwright test. Playwright sends each test to the matching Browserless browser.
6. Debug failed tests with Trace Viewer
With trace: 'on-first-retry' set, Playwright writes a trace.zip to test-results/ whenever a test fails its first run. Open it locally:
npx playwright show-trace test-results/<test-name>/trace.zip
The Trace Viewer gives you a step-by-step timeline with DOM snapshots, network requests, and console logs. No SSH or access to the remote machine needed.
7. Check the output
Running 4 tests using 4 workers
✓ [chromium] › example.spec.js:3 › page has correct title (1.2s)
✓ [chromium] › example.spec.js:8 › heading is visible and correct (0.9s)
✓ [chromium] › example.spec.js:14 › page contains a link (0.7s)
✓ [chromium] › example.spec.js:20 › page loads without analytics calls (1.1s)
4 passed (1.5s)
1. Install dependencies
pip install pytest pytest-playwright playwright pytest-xdist
playwright install
2. Configure Browserless
Override the browser fixture in conftest.py to connect to Browserless instead of launching a local browser. The scope="session" means a single browser connection is shared across all tests in the run. Individual tests each get their own page from the default page fixture:
# conftest.py
import pytest
from playwright.sync_api import Playwright
@pytest.fixture(scope="session")
def browser(playwright: Playwright):
browser = playwright.chromium.connect(
"wss://production-sfo.browserless.io/chromium/playwright?token=YOUR_API_TOKEN_HERE"
)
yield browser
browser.close()
3. Write tests
# tests/test_example.py
import re
from playwright.sync_api import expect
def test_page_has_correct_title(page):
page.goto("https://scraping-sandbox.netlify.app/products")
expect(page).to_have_title(re.compile(r"Product Catalog"))
def test_heading_is_visible(page):
page.goto("https://scraping-sandbox.netlify.app/products")
heading = page.locator("h1")
expect(heading).to_be_visible()
expect(heading).to_have_text("Product Catalog")
def test_page_has_link(page):
page.goto("https://scraping-sandbox.netlify.app/products")
expect(page.locator("a[href]").first).to_be_visible()
def test_page_loads_without_analytics(page):
blocked = []
def handle_route(route):
blocked.append(route.request.url)
route.abort()
page.route("**/analytics/**", handle_route)
page.goto("https://scraping-sandbox.netlify.app/products")
expect(page).to_have_title(re.compile(r"Product Catalog"))
print(f"Blocked {len(blocked)} analytics request(s)")
4. Run the tests
pytest tests/ -n 4
The -n 4 flag (from pytest-xdist) runs 4 tests in parallel, each getting its own Browserless session.
5. Cross-browser testing
Parameterize the browser fixture so every test runs against Chromium, Firefox, and WebKit. Each parameterized value produces a separate browser instance connecting to its own endpoint:
# conftest.py
import pytest
from playwright.sync_api import Playwright
TOKEN = "YOUR_API_TOKEN_HERE"
ENDPOINTS = {
"chromium": f"wss://production-sfo.browserless.io/chromium/playwright?token={TOKEN}",
"firefox": f"wss://production-sfo.browserless.io/firefox/playwright?token={TOKEN}",
"webkit": f"wss://production-sfo.browserless.io/webkit/playwright?token={TOKEN}",
}
@pytest.fixture(scope="session", params=["chromium", "firefox", "webkit"])
def browser(playwright: Playwright, request):
name = request.param
b = getattr(playwright, name).connect(ENDPOINTS[name])
yield b
b.close()
6. Debug with Trace Viewer
Pass --tracing retain-on-failure to capture traces only for failing tests, so you're not storing trace zips for every passing run:
pytest --tracing retain-on-failure
Traces are saved to test-results/. Open one with:
playwright show-trace test-results/<test-name>/trace.zip
7. Check the output
PASSED tests/test_example.py::test_page_has_correct_title
PASSED tests/test_example.py::test_heading_is_visible
PASSED tests/test_example.py::test_page_has_link
PASSED tests/test_example.py::test_page_loads_without_analytics
4 passed in 2.3s
1. Install dependencies
Add to pom.xml:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.40.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
2. Set up the test class
Connect to Browserless once in @BeforeAll so the WebSocket handshake only happens once per suite. Open a fresh context per test in @BeforeEach. Separate contexts give each test isolated cookies, storage, and network state, so tests can't bleed state into each other:
import com.microsoft.playwright.*;
import org.junit.jupiter.api.*;
class ExampleTest {
static Playwright playwright;
static Browser browser;
BrowserContext context;
Page page;
@BeforeAll
static void connect() {
playwright = Playwright.create();
browser = playwright.chromium().connect(
"wss://production-sfo.browserless.io/chromium/playwright?token=YOUR_API_TOKEN_HERE"
);
}
@AfterAll
static void disconnect() {
playwright.close();
}
@BeforeEach
void createContextAndPage() {
context = browser.newContext();
page = context.newPage();
}
@AfterEach
void closeContext() {
context.close();
}
}
3. Write tests
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import java.util.*;
import java.util.regex.Pattern;
@Test
void pageHasCorrectTitle() {
page.navigate("https://scraping-sandbox.netlify.app/products");
assertThat(page).hasTitle(Pattern.compile("Product Catalog"));
}
@Test
void headingIsVisible() {
page.navigate("https://scraping-sandbox.netlify.app/products");
Locator heading = page.locator("h1");
assertThat(heading).isVisible();
assertThat(heading).hasText("Product Catalog");
}
@Test
void pageHasLink() {
page.navigate("https://scraping-sandbox.netlify.app/products");
assertThat(page.locator("a[href]").first()).isVisible();
}
@Test
void pageLoadsWithoutAnalytics() {
List<String> blocked = new ArrayList<>();
page.route("**/analytics/**", route -> {
blocked.add(route.request().url());
route.abort();
});
page.navigate("https://scraping-sandbox.netlify.app/products");
assertThat(page).hasTitle(Pattern.compile("Product Catalog"));
System.out.println("Blocked " + blocked.size() + " analytics request(s)");
}
4. Run the tests
mvn test
To run in parallel, configure the Surefire plugin in pom.xml:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<parallel>methods</parallel>
<threadCount>4</threadCount>
</configuration>
</plugin>
5. Cross-browser testing
Use @ParameterizedTest with @MethodSource to run the same test method against each browser endpoint. Each invocation opens its own Playwright instance so browser types don't share state:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import java.util.stream.Stream;
static Stream<Arguments> browserEndpoints() {
String token = "YOUR_API_TOKEN_HERE";
String base = "wss://production-sfo.browserless.io";
return Stream.of(
Arguments.of("chromium", base + "/chromium/playwright?token=" + token),
Arguments.of("firefox", base + "/firefox/playwright?token=" + token),
Arguments.of("webkit", base + "/webkit/playwright?token=" + token)
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("browserEndpoints")
void pageHasCorrectTitle(String browserName, String wsEndpoint) {
try (Playwright pw = Playwright.create()) {
Browser b = pw.chromium().connect(wsEndpoint);
Page p = b.newPage();
p.navigate("https://scraping-sandbox.netlify.app/products");
assertThat(p).hasTitle(Pattern.compile("Product Catalog"));
}
}
6. Debug with Trace Viewer
Start and stop tracing in @BeforeEach / @AfterEach so each test gets its own trace file:
import java.nio.file.Paths;
import org.junit.jupiter.api.TestInfo;
@BeforeEach
void createContextAndPage() {
context = browser.newContext();
context.tracing().start(new Tracing.StartOptions()
.setScreenshots(true).setSnapshots(true));
page = context.newPage();
}
@AfterEach
void closeContext(TestInfo testInfo) {
context.tracing().stop(new Tracing.StopOptions()
.setPath(Paths.get("trace-" + testInfo.getDisplayName() + ".zip")));
context.close();
}
Open the trace:
mvn exec:java -e -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="show-trace trace.zip"
7. Check the output
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
1. Install dependencies
dotnet add package Microsoft.Playwright.NUnit
2. Set up the test class
Connect to Browserless once in [OneTimeSetUp] so the WebSocket handshake only happens once per suite. Open a fresh context per test in [SetUp]. Separate contexts give each test isolated cookies, storage, and network state, so tests can't bleed state into each other:
using Microsoft.Playwright;
using NUnit.Framework;
using System.Collections.Generic;
using System.Threading.Tasks;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class ExampleTests
{
private IPlaywright _playwright;
private IBrowser _browser;
private IBrowserContext _context;
private IPage _page;
[OneTimeSetUp]
public async Task OneTimeSetUp()
{
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.ConnectAsync(
"wss://production-sfo.browserless.io/chromium/playwright?token=YOUR_API_TOKEN_HERE"
);
}
[SetUp]
public async Task SetUp()
{
_context = await _browser.NewContextAsync();
_page = await _context.NewPageAsync();
}
[TearDown]
public async Task TearDown()
{
await _context.CloseAsync();
}
[OneTimeTearDown]
public async Task OneTimeTearDown()
{
await _playwright.DisposeAsync();
}
}
3. Write tests
using System.Text.RegularExpressions;
[Test]
public async Task PageHasCorrectTitle()
{
await _page.GotoAsync("https://scraping-sandbox.netlify.app/products");
await Assertions.Expect(_page).ToHaveTitleAsync(new Regex("Product Catalog"));
}
[Test]
public async Task HeadingIsVisible()
{
await _page.GotoAsync("https://scraping-sandbox.netlify.app/products");
var heading = _page.Locator("h1");
await Assertions.Expect(heading).ToBeVisibleAsync();
await Assertions.Expect(heading).ToHaveTextAsync("Product Catalog");
}
[Test]
public async Task PageHasLink()
{
await _page.GotoAsync("https://scraping-sandbox.netlify.app/products");
await Assertions.Expect(_page.Locator("a[href]").First).ToBeVisibleAsync();
}
[Test]
public async Task PageLoadsWithoutAnalytics()
{
var blocked = new List<string>();
await _page.RouteAsync("**/analytics/**", async route =>
{
blocked.Add(route.Request.Url);
await route.AbortAsync();
});
await _page.GotoAsync("https://scraping-sandbox.netlify.app/products");
await Assertions.Expect(_page).ToHaveTitleAsync(new Regex("Product Catalog"));
Console.WriteLine($"Blocked {blocked.Count} analytics request(s)");
}
4. Run the tests
dotnet test
To configure parallelism, add a .runsettings file:
<RunSettings>
<RunConfiguration>
<MaxCpuCount>4</MaxCpuCount>
</RunConfiguration>
</RunSettings>
Then run with dotnet test --settings .runsettings.
5. Cross-browser testing
Use NUnit's [TestCaseSource] to feed each browser endpoint as a separate test case. Each case creates its own Playwright instance so browser types don't share state:
private static IEnumerable<TestCaseData> BrowserEndpoints()
{
var token = "YOUR_API_TOKEN_HERE";
var @base = "wss://production-sfo.browserless.io";
yield return new TestCaseData("chromium", $"{@base}/chromium/playwright?token={token}");
yield return new TestCaseData("firefox", $"{@base}/firefox/playwright?token={token}");
yield return new TestCaseData("webkit", $"{@base}/webkit/playwright?token={token}");
}
[Test, TestCaseSource(nameof(BrowserEndpoints))]
public async Task PageHasCorrectTitle(string browserName, string wsEndpoint)
{
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectAsync(wsEndpoint);
var page = await browser.NewPageAsync();
await page.GotoAsync("https://scraping-sandbox.netlify.app/products");
await Assertions.Expect(page).ToHaveTitleAsync(new Regex("Product Catalog"));
await playwright.DisposeAsync();
}
6. Debug with Trace Viewer
Start tracing in [SetUp] and stop it in [TearDown] so each test gets its own trace file:
[SetUp]
public async Task SetUp()
{
_context = await _browser.NewContextAsync();
await _context.Tracing.StartAsync(new TracingStartOptions
{
Screenshots = true,
Snapshots = true,
});
_page = await _context.NewPageAsync();
}
[TearDown]
public async Task TearDown()
{
await _context.Tracing.StopAsync(new TracingStopOptions
{
Path = $"trace-{TestContext.CurrentContext.Test.Name}.zip",
});
await _context.CloseAsync();
}
Open the trace:
pwsh bin/Debug/net8.0/playwright.ps1 show-trace trace.zip
7. Check the output
Test Run Successful.
Total tests: 4
Passed: 4
Total time: 2.5000 Seconds
1. Write the mutation
Navigate to the page and select the content you want to verify. BQL returns typed structured data, so you write your assertions in your own application code after the response arrives. No assertion library runs inside Browserless:
mutation E2ECheck {
goto(url: "https://scraping-sandbox.netlify.app/products", waitUntil: domContentLoaded) {
status
}
title: mapSelector(selector: "title") {
innerText
}
heading: mapSelector(selector: "h1") {
innerText
}
links: mapSelector(selector: "a[href]") {
href: attribute(name: "href") {
value
}
}
}
2. Run it
Paste into the BQL IDE and click Run.
3. Check the output
{
"data": {
"goto": { "status": 200 },
"title": [{ "innerText": "Product Catalog" }],
"heading": [{ "innerText": "Product Catalog" }],
"links": [{ "href": { "value": "https://scraping-sandbox.netlify.app/contact-us" } }]
}
}
Assert against the values in your application code. Check goto.status for the HTTP status code, innerText fields for content, array lengths for element counts, and attribute fields for link targets.