use browserbase::{
Browserbase,
models::{CreateSessionRequest, ListSessionsParams, UpdateSessionRequest},
};
use serde_json::json;
use wiremock::{
Mock, MockServer, ResponseTemplate,
matchers::{body_json, header, method, path, query_param},
};
#[tokio::test]
async fn create_session_sends_api_key_and_parses_response() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("POST"))
.and(path("/v1/sessions"))
.and(header("X-BB-API-Key", "test-key"))
.and(body_json(json!({"projectId":"proj_123"})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "sess_1",
"projectId": "proj_123",
"status": "RUNNING",
"connectUrl": "wss://example/connect"
})))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let session = bb
.sessions()
.create(CreateSessionRequest::new("proj_123"))
.await
.unwrap();
assert_eq!(session.id, "sess_1");
assert_eq!(session.project_id.as_deref(), Some("proj_123"));
assert_eq!(session.status.as_deref(), Some("RUNNING"));
assert_eq!(
session.connect_url.as_deref(),
Some("wss://example/connect")
);
}
#[tokio::test]
async fn list_sessions_supports_query_params() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("GET"))
.and(path("/v1/sessions"))
.and(header("X-BB-API-Key", "test-key"))
.and(query_param("status", "RUNNING"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!([
{"id": "sess_1"},
{"id": "sess_2"}
])))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let sessions = bb
.sessions()
.list(ListSessionsParams::new().status("RUNNING"))
.await
.unwrap();
assert_eq!(sessions.len(), 2);
assert_eq!(sessions[0].id, "sess_1");
assert_eq!(sessions[1].id, "sess_2");
}
#[tokio::test]
async fn update_session_posts_to_session_id() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("POST"))
.and(path("/v1/sessions/sess_1"))
.and(header("X-BB-API-Key", "test-key"))
.and(body_json(json!({
"projectId": "proj_123",
"status": "REQUEST_RELEASE"
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "sess_1",
"status": "REQUEST_RELEASE"
})))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let session = bb
.sessions()
.update(
"sess_1",
UpdateSessionRequest::new("proj_123", "REQUEST_RELEASE"),
)
.await
.unwrap();
assert_eq!(session.id, "sess_1");
assert_eq!(session.status.as_deref(), Some("REQUEST_RELEASE"));
}
#[tokio::test]
async fn downloads_zip_returns_raw_bytes() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("GET"))
.and(path("/v1/sessions/sess_1/downloads"))
.and(header("X-BB-API-Key", "test-key"))
.respond_with(ResponseTemplate::new(200).set_body_bytes(vec![1u8, 2, 3]))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let bytes = bb.sessions().downloads_zip("sess_1").await.unwrap();
assert_eq!(&bytes[..], &[1u8, 2, 3]);
}
#[tokio::test]
async fn get_session_returns_session_details() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("GET"))
.and(path("/v1/sessions/sess_1"))
.and(header("X-BB-API-Key", "test-key"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "sess_1",
"projectId": "proj_123",
"status": "RUNNING",
"connectUrl": "wss://example/connect",
"region": "us-west-2"
})))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let session = bb.sessions().get("sess_1").await.unwrap();
assert_eq!(session.id, "sess_1");
assert_eq!(session.region.as_deref(), Some("us-west-2"));
}
#[tokio::test]
async fn delete_session_returns_unit() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("DELETE"))
.and(path("/v1/sessions/sess_1"))
.and(header("X-BB-API-Key", "test-key"))
.respond_with(ResponseTemplate::new(204))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
bb.sessions().delete("sess_1").await.unwrap();
}
#[tokio::test]
async fn create_session_with_optional_fields_sends_all_params() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("POST"))
.and(path("/v1/sessions"))
.and(header("X-BB-API-Key", "test-key"))
.and(body_json(json!({
"projectId": "proj_123",
"region": "eu-central-1",
"keepAlive": true,
"contextId": "ctx_456",
"userMetadata": {"tag": "test"}
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "sess_1",
"projectId": "proj_123",
"status": "RUNNING"
})))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let req = CreateSessionRequest::new("proj_123")
.region("eu-central-1")
.keep_alive(true)
.context_id("ctx_456")
.user_metadata(json!({"tag": "test"}));
let session = bb.sessions().create(req).await.unwrap();
assert_eq!(session.id, "sess_1");
}
#[tokio::test]
async fn session_logs_returns_json() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("GET"))
.and(path("/v1/sessions/sess_1/logs"))
.and(header("X-BB-API-Key", "test-key"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"logs": [],
"total": 0
})))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let logs = bb.sessions().logs("sess_1").await.unwrap();
assert!(logs.is_object());
}
#[tokio::test]
async fn create_upload_multipart_file() {
use wiremock::matchers::header_exists;
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("POST"))
.and(path("/v1/sessions/sess_1/uploads"))
.and(header_exists("X-BB-API-Key"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"success": true,
"filename": "upload.bin"
})))
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let result = bb
.sessions()
.create_upload_from_bytes("sess_1", "upload.bin", bytes::Bytes::from(vec![1, 2, 3]))
.await
.unwrap();
assert!(result.is_object());
}
#[tokio::test]
async fn api_error_on_429_status() {
let server = MockServer::start().await;
let base_url = format!("{}/v1/", server.uri());
Mock::given(method("POST"))
.and(path("/v1/sessions"))
.and(header("X-BB-API-Key", "test-key"))
.respond_with(
ResponseTemplate::new(429)
.set_body_json(json!({"error": "Too Many Requests", "message": "Limit reached"})),
)
.mount(&server)
.await;
let bb = Browserbase::builder("test-key")
.unwrap()
.base_url(&base_url)
.unwrap()
.build()
.unwrap();
let result = bb
.sessions()
.create(CreateSessionRequest::new("proj_123"))
.await;
assert!(result.is_err());
let err = result.unwrap_err();
match err {
browserbase::error::Error::Api { status, body } => {
assert_eq!(status.as_u16(), 429);
assert!(body.contains("Too Many Requests"));
}
_ => panic!("Expected API error"),
}
}