Watching Sessions
The Browserless Docker image and Dedicated hosted plans support real-time session watching for debugging and monitoring.
This guide covers three methods for watching sessions:
- Using the Docker image session viewer
- Using the Sessions tab in Dedicated plans
- Programmatically watching sessions via GraphQL API
Watching sessions on the Docker image
To use the session viewer on the Docker image, you don't need to do anything special in your code or how you connect to Browserless. Optionally, you can add an id parameter to your connect call to sort or filter sessions to the one you care about.
Start the container as usual:
$ docker run --rm -p 3000:3000 ghcr.io/browserless/chrome
Then, in your application or script connect to it.
- Puppeteer
- Playwright
import puppeteer from "puppeteer";
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://YOUR_CONTAINER_URL_HERE?token=YOUR_API_TOKEN_HERE`,
});
const page = await browser.newPage();
await page.goto("https://example.com/");
await browser.close();
- Javascript
- Python
- Java
- C#
import { chromium } from "playwright-core";
(async () => {
const browser = await chromium.connectOverCDP(
`wss://YOUR_CONTAINER_URL_HERE?token=YOUR_API_TOKEN_HERE`
);
// Create a new context and page
const context = browser.contexts()[0] || (await browser.newContext());
const page = await context.newPage();
// Navigate to the URL
await page.goto("https://example.com/");
console.log("Page loaded successfully");
// Close the browser
await browser.close();
})();
import asyncio
from playwright.async_api import async_playwright
WS_ENDPOINT = "wss://YOUR_CONTAINER_URL_HERE?token=YOUR_API_TOKEN_HERE"
async def main():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(WS_ENDPOINT)
# Use existing context or create a new one
context = browser.contexts[0] if browser.contexts else await browser.new_context()
page = await context.new_page()
await page.goto("https://example.com/")
print("Page loaded successfully")
await browser.close()
asyncio.run(main())
import com.microsoft.playwright.*;
public class PlaywrightRemoteBrowser {
public static void main(String[] args) {
String wsEndpoint = "wss://YOUR_CONTAINER_URL_HERE?token=YOUR_API_TOKEN_HERE";
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connectOverCDP(wsEndpoint);
// Use existing context or create a new one
BrowserContext context = browser.contexts().isEmpty()
? browser.newContext()
: browser.contexts().get(0);
Page page = context.newPage();
page.navigate("https://example.com/");
System.out.println("Page loaded successfully");
browser.close();
}
}
}
using System;
using System.Threading.Tasks;
using Microsoft.Playwright;
class Program
{
static async Task Main(string[] args)
{
string wsEndpoint = "wss://YOUR_CONTAINER_URL_HERE?token=YOUR_API_TOKEN_HERE";
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(wsEndpoint);
// Use existing context or create a new one
var context = browser.Contexts.Count > 0
? browser.Contexts[0]
: await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync("https://example.com/");
Console.WriteLine("Page loaded successfully");
await browser.CloseAsync();
}
}
In your browser, you can go to http://localhost:3000/sessions to get a JSON of the running sessions.
If your session is immediately closing, then you can keep it open by not calling browser.close at the end (just remember to terminate your script with CTRL+C or similar).
Using the Sessions tab in Dedicated plans
In Dedicated plans, you can see your active sessions at any time by going to the Sessions portion of the account page.
To view running sessions:
- Click the "Fetch Running Sessions" button to retrieve actively running sessions across all your workers
- Click the "Debugger" link to open and view specific sessions
Sessions appear in the list immediately after connecting. Refresh by clicking "Fetch Running Sessions".
Programmatically Watching Sessions on Dedicated Plans
The GraphQL API lets you retrieve running sessions programmatically. Common use cases:
- End-users can watch their automation running in real time
- Engineers can debug live production issues
- Teams can pinpoint problems in scripts faster
Security note: When setting this up, ensure you don't accidentally expose non-relevant sessions among your users. Use a unique id parameter to filter and distinguish between running sessions.
Here's a complete example showing how to connect a browser, fetch the session via GraphQL, and get a live debugging URL.
- Puppeteer
- Playwright
import puppeteer from "puppeteer";
const apiToken = "YOUR_API_TOKEN_HERE";
const id = "some-unique-id";
const getSessionsById = (id) => {
return fetch("https://api.browserless.io/graphql", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
query: `
query getSessions($id: String!, $apiToken: String!) {
sessions(apiToken: $apiToken, id: $id) {
description
devtoolsFrontendUrl
id
title
url
browserId
browserWSEndpoint
}
}
`,
variables: {
id,
apiToken,
},
}),
})
.then((res) => res.json())
.then((res) => res.data.sessions)
.catch((error) => {
console.log(`Error retrieving sessions ${error.message}`);
return null;
});
};
const run = async () => {
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
let browser = null;
try {
browser = await puppeteer.connect({
browserWSEndpoint: `wss://YOUR_CONTAINER_URL_HERE?token=${apiToken}&id=${id}`,
});
const page = await browser.newPage();
await page.goto("https://example.com");
const [session] = await getSessionsById(id);
if (!session) {
throw new Error(`Error retrieving session!`);
}
console.log(`Open: ${session.devtoolsFrontendUrl} in your browser!`);
// Let the page stay open for 30 seconds so we can debug!
await sleep(30000);
} catch (error) {
console.error(`Saw error when running: ${error.message}`);
} finally {
if (browser) {
console.log(`Shutting down the browser.`);
browser.close();
}
}
};
run();
- Javascript
- Python
- Java
- C#
import { chromium } from "playwright-core";
const apiToken = "YOUR_API_TOKEN_HERE";
const id = "some-unique-id";
const getSessionsById = async (id) => {
try {
const response = await fetch("https://api.browserless.io/graphql", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
query: `
query getSessions($id: String!, $apiToken: String!) {
sessions(apiToken: $apiToken, id: $id) {
description
devtoolsFrontendUrl
id
title
url
id
browserId
browserWSEndpoint
}
}
`,
variables: {
id,
apiToken,
},
}),
});
const result = await response.json();
return result.data.sessions;
} catch (error) {
console.error(`Error retrieving sessions: ${error.message}`);
return null;
}
};
const run = async () => {
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let browser = null;
try {
browser = await chromium.connectOverCDP(
`wss://YOUR_CONTAINER_URL_HERE?token=${apiToken}&id=${id}`
);
const context = browser.contexts()[0] || (await browser.newContext());
const page = await context.newPage();
await page.goto("https://example.com");
const [session] = await getSessionsById(id);
if (!session) {
throw new Error(`Error retrieving session!`);
}
console.log(`Open: ${session.devtoolsFrontendUrl} in your browser!`);
// Let the page stay open for 30 seconds so we can debug!
await sleep(30000);
} catch (error) {
console.error(`Saw error when running: ${error.message}`);
} finally {
if (browser) {
console.log(`Shutting down the browser.`);
await browser.close();
}
}
};
run();
import asyncio
import requests
from playwright.async_api import async_playwright
API_TOKEN = "YOUR_API_TOKEN_HERE"
ID = "some-unique-id"
def get_sessions_by_id(session_id):
try:
response = requests.post(
"https://api.browserless.io/graphql",
json={
"query": """
query getSessions($id: String!, $apiToken: String!) {
sessions(apiToken: $apiToken, id: $id) {
description
devtoolsFrontendUrl
id
title
url
browserId
browserWSEndpoint
}
}
""",
"variables": {
"id": session_id,
"apiToken": API_TOKEN,
},
},
headers={"Content-Type": "application/json"},
)
response.raise_for_status()
return response.json()["data"]["sessions"]
except Exception as e:
print(f"Error retrieving sessions: {e}")
return None
async def run():
async with async_playwright() as p:
browser = None
try:
browser = await p.chromium.connect_over_cdp(
f"wss://YOUR_CONTAINER_URL_HERE?token={API_TOKEN}&id={ID}"
)
context = browser.contexts[0] if browser.contexts else await browser.new_context()
page = await context.new_page()
await page.goto("https://example.com")
sessions = get_sessions_by_id(ID)
if not sessions:
raise Exception("Error retrieving session!")
session = sessions[0]
print(f"Open: {session['devtoolsFrontendUrl']} in your browser!")
# Let the page stay open for 30 seconds
await asyncio.sleep(30)
except Exception as e:
print(f"Saw error when running: {e}")
finally:
if browser:
print("Shutting down the browser.")
await browser.close()
asyncio.run(run())
import com.microsoft.playwright.*;
import java.net.http.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.util.*;
import com.google.gson.Gson;
public class PlaywrightRemote {
private static final String API_TOKEN = "YOUR_API_TOKEN_HERE";
private static final String ID = "some-unique-id";
private static List<Map<String, Object>> getSessionsById(String id) throws Exception {
HttpClient client = HttpClient.newHttpClient();
String query = """
query getSessions($id: String!, $apiToken: String!) {
sessions(apiToken: $apiToken, id: $id) {
description
devtoolsFrontendUrl
id
title
url
browserId
browserWSEndpoint
}
}
""";
Map<String, Object> payload = Map.of(
"query", query,
"variables", Map.of("id", id, "apiToken", API_TOKEN)
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.browserless.io/graphql"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(new Gson().toJson(payload)))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new Exception("Error retrieving sessions: " + response.body());
}
Map<String, Object> responseData = new Gson().fromJson(response.body(), Map.class);
return (List<Map<String, Object>>) ((Map<String, Object>) responseData.get("data")).get("sessions");
}
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connectOverCDP(
"wss://YOUR_CONTAINER_URL_HERE?token=" + API_TOKEN + "&id=" + ID
);
BrowserContext context = browser.contexts().isEmpty() ? browser.newContext() : browser.contexts().get(0);
Page page = context.newPage();
page.navigate("https://example.com");
List<Map<String, Object>> sessions = getSessionsById(ID);
if (sessions.isEmpty()) {
throw new Exception("Error retrieving session!");
}
Map<String, Object> session = sessions.get(0);
System.out.println("Open: " + session.get("devtoolsFrontendUrl") + " in your browser!");
Thread.sleep(30000);
browser.close();
} catch (Exception e) {
System.err.println("Saw error when running: " + e.getMessage());
}
}
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Playwright;
class Program
{
private const string ApiToken = "YOUR_API_TOKEN_HERE";
private const string Id = "some-unique-id";
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
var playwright = await Playwright.CreateAsync();
IBrowser browser = null;
try
{
browser = await playwright.Chromium.ConnectOverCDPAsync(
$"wss://YOUR_CONTAINER_URL_HERE?token={ApiToken}&id={Id}"
);
var context = browser.Contexts.Count > 0 ? browser.Contexts[0] : await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync("https://example.com");
var payload = new
{
query = @"
query getSessions($id: String!, $apiToken: String!) {
sessions(apiToken: $apiToken, id: $id) {
description
devtoolsFrontendUrl
id
title
url
browserId
browserWSEndpoint
}
}
",
variables = new { id = Id, apiToken = ApiToken }
};
var response = await httpClient.PostAsync(
"https://api.browserless.io/graphql",
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
);
response.EnsureSuccessStatusCode();
var responseData = JsonSerializer.Deserialize<JsonElement>(await response.Content.ReadAsStringAsync());
var sessions = responseData.GetProperty("data").GetProperty("sessions").EnumerateArray();
if (!sessions.MoveNext())
{
throw new Exception("Error retrieving session!");
}
var session = sessions.Current;
Console.WriteLine($"Open: {session.GetProperty("devtoolsFrontendUrl").GetString()} in your browser!");
await Task.Delay(30000);
}
catch (Exception e)
{
Console.WriteLine($"Saw error when running: {e.Message}");
}
finally
{
if (browser != null)
{
Console.WriteLine("Shutting down the browser.");
await browser.CloseAsync();
}
}
}
}
The examples include a page.waitFor call to give you time to visit the debugging page. In production, page interactions happen quickly and you may miss the session. To watch the script from the start, add the &pause query-string parameter to your puppeteer.connect call.
Selecting Modules
The examples use puppeteer or playwright, but you can use any library including Selenium. Puppeteer and Playwright have the best support.
The underlying GraphQL query retrieves session properties. Only devtoolsFrontendUrl is used in these examples. The other fields are included for reference.
query getSessions($id: String!, $apiToken: String!) {
sessions(apiToken: $apiToken, id: $id) {
description
devtoolsFrontendUrl
id
title
url
id
browserId
browserWSEndpoint
}
}
This query retrieves sessions by API token and id. The response looks like:
{
"data": {
"sessions": [
{
"id": null,
"initialConnectURL": "wss://YOUR_CONTAINER_URL_HERE/firefox/playwright/?token=YOUR_API_TOKEN_HERE",
"isTempDataDir": true,
"launchOptions": {},
"numbConnected": 1,
"routePath": ["/firefox/playwright", "/firefox/playwright"],
// ...
}
]
}
}
In all of our returned links we omit your API token, so you'll need to add it to the link manually to visit it. This is done for security purposes.
For questions or help with setup, contact us.