Best BQL Practices
We recommend you use the following best practices to make the most out of BQL. This guide covers essential patterns that will make your browser automation more reliable and maintainable.
Prefer Variables Over Hardcoded Values
Use GraphQL variables to keep your BQL operations reusable and easy to maintain. Variables let you parameterize values like URLs, selectors, and input data, so you can avoid hardcoding and repeating them across queries.
- ✅ Good - Using Variables
- ❌ Bad - Hardcoded Values
# Avoiding concatenating strings by using GraphQL variables
mutation LoginUser($email: String!, $password: String!, $loginUrl: String!) {
goto(url: $loginUrl, waitUntil: firstContentfulPaint) {
status
}
typeEmail: type(
selector: "input[type='email']"
text: $email
) {
time
}
typePassword: type(
selector: "input[type='password']"
text: $password
) {
time
}
submitLogin: click(selector: "button[type='submit']") {
x
y
}
}
Then, send a variables
object in your JSON payload read more about them here
{
"email": "user@example.com",
"password": "securepassword123",
"loginUrl": "https://example.com/login"
}
mutation LoginUser {
goto(url: "https://example.com/login", waitUntil: firstContentfulPaint) {
status
}
# Hardcoding your credentials!
typeEmail: type(
selector: "input[type='email']"
text: "user@example.com"
) {
time
}
typePassword: type(
selector: "input[type='password']"
text: "securepassword123"
) {
time
}
submitLogin: click(selector: "button[type='submit']") {
x
y
}
}
Prefer Semantic Selectors Over Flaky CSS Classes
Prefer semantic selectors for more reliable automations. Use stable HTML attributes, roles, or other meaningful selectors that are less likely to change, and avoid relying on CSS classes that can break with UI updates.
- ✅ Good - Semantic Selectors
- ❌ Bad - Flaky CSS Classes
mutation AddProductToCart($url: String!) {
goto(url: $url, waitUntil: networkIdle) {
status
}
# Use HTML attributes for more reliable scraping
click(selector: "[data-testid='add-to-cart-button']") {
selector
}
# Use HTML roles instead of classes for waiting for a particular selector
waitForSelector(
selector: "[role='status'][aria-label='cart-item-count']"
visible: true
) {
selector
}
html(visible: true) {
html
}
}
mutation AddProductToCart($url: String!) {
goto(url: $url) {
status
}
# Use an unstable, minified class name
click(selector: ".btn-345xy") {
selector
}
# Wait for cart count using a brittle nth-child selector
waitForSelector(selector: ".header > div:nth-child(3) > span", visible: true) {
selector
}
html(visible: true) {
html
}
}
Reject Unnecessary Resources
Block unnecessary resources to improve performance. Use the reject mutation to prevent loading ads, images, videos, and other non-essential requests, reducing bandwidth usage and speeding up execution. It's important to keep in mind that reducing bandwidth usage may reduce your proxy cost if they are enabled, however they could also trigger bot detection on some sites.
- ✅ Good - Block Unnecessary Resources
- ❌ Bad - Loading Everything
# Make sure to enable the Adblock parameter in the editor!
mutation Search($url: String!, $query: String!) {
# Reject images, styles and media right away
reject(
type: [image, stylesheet, media]
) {
time
}
goto(url: $url, waitUntil: networkIdle) {
status
}
type(text: $query, selector: "input[name='q']") {
selector
}
click(selector: "button[type='submit']") {
selector
}
waitForNavigation {
status
}
html(visible: true) {
html
}
}
mutation Search($url: String!, $query: String!) {
# Waits until all images, styles, and requests are done loading
# before continuing
goto(url: $url, waitUntil: networkIdle) {
status
}
type(text: $query, selector: "input[name='q']") {
selector
}
click(selector: "button[type='submit']") {
selector
}
waitForNavigation {
status
}
html(visible: true) {
html
}
}
Summary
The tl; dr is:
- Use variables instead of hardcoded values
- Use semantic selectors instead of CSS classes
- Block unnecessary resources to improve performance