Chat widgets are great for customer support, but they can wreak havoc on your automated tests.
These floating elements often interfere with Playwright tests by covering clickable buttons, triggering unexpected popups, or causing element selection issues.
If you've ever had a test fail because a chat widget appeared at the wrong moment, you're not alone.
This guide shows you exactly how to block popular chat widgets like Drift, Intercom, Zendesk, and others during your Playwright test runs.
You'll learn practical techniques to prevent widget interference while maintaining reliable, consistent test execution.
Why chat widgets interfere with Playwright tests
Chat widgets create several challenges for automated testing that can turn reliable tests into flaky nightmares.
Understanding these issues helps you implement the right blocking strategies for your test suite.
Element visibility problems
Chat widgets often float over page content, making elements unclickable or invisible to Playwright's selectors.
This commonly happens when:
- Widgets cover form buttons or navigation elements
- Popup notifications appear during critical test steps
- Widget animations trigger during element interactions
Timing and race conditions
Widgets load asynchronously and can appear at unpredictable moments during test execution.
This creates timing issues where:
- Tests pass locally but fail in CI environments
- Widget loading delays affect test performance
- Race conditions occur between widget initialization and test actions
Network requests and performance
Chat widgets make additional network requests that can:
- Slow down page load times
- Cause timeouts in test environments
- Interfere with network mocking or stubbing
Focus and interaction conflicts
Widgets can steal focus from form elements or trigger unwanted interactions:
- Auto-focus on chat input fields
- Keyboard shortcuts captured by widget
- Mouse events intercepted by widget overlays
Blocking Drift chat widget in Playwright tests
Drift is one of the most popular chat widgets, and blocking it requires intercepting its domain requests during test execution.
Here's a comprehensive approach to prevent Drift from loading in your tests.
Basic Drift blocking function
Create a reusable helper function to block all Drift-related requests:
import { Page } from '@playwright/test'
export async function blockDrift(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('drift.com') || url.includes('driftt.com')) {
route.abort()
} else {
route.continue()
}
})
}
Using the blocking function in tests
Implement the blocking function in your test setup:
import { Page, test, expect } from '@playwright/test'
import { blockDrift } from '../helpers/block-drift'
let page: Page
test.beforeEach(async ({ browser }) => {
page = await browser.newPage()
await blockDrift(page)
await page.goto('https://your-website.com')
})
test('user can complete checkout without widget interference', async () => {
// Your test steps here - no Drift widget will appear
await page.click('[data-testid="checkout-button"]')
await expect(page.locator('.checkout-form')).toBeVisible()
})
Advanced Drift blocking with multiple domains
Drift uses several domains and subdomains. Block them all for complete coverage:
export async function blockDriftAdvanced(page: Page) {
const driftDomains = [
'drift.com',
'driftt.com',
'drift-widget.com',
'js.driftt.com',
'event.drift.com'
]
await page.route('**/*', route => {
const url = route.request().url()
const shouldBlock = driftDomains.some(domain => url.includes(domain))
if (shouldBlock) {
route.abort()
} else {
route.continue()
}
})
}
Blocking with custom error handling
Add logging and error handling for better debugging:
export async function blockDriftWithLogging(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('drift.com') || url.includes('driftt.com')) {
console.log(`Blocked Drift request: ${url}`)
route.abort()
} else {
route.continue()
}
})
}
Blocking other popular chat widgets
Different chat platforms use various domains and loading mechanisms. Here are blocking functions for the most common widgets.
Intercom blocking
export async function blockIntercom(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('intercom.io') || url.includes('intercom.com')) {
route.abort()
} else {
route.continue()
}
})
}
Zendesk Chat blocking
export async function blockZendeskChat(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('zendesk.com') || url.includes('zdassets.com')) {
route.abort()
} else {
route.continue()
}
})
}
HubSpot Chat blocking
export async function blockHubSpotChat(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('hubspot.com') || url.includes('hs-scripts.com')) {
route.abort()
} else {
route.continue()
}
})
}
Crisp Chat blocking
export async function blockCrispChat(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('crisp.chat') || url.includes('crisp.help')) {
route.abort()
} else {
route.continue()
}
})
}
LiveChat blocking
export async function blockLiveChat(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('livechatinc.com') || url.includes('livechat.com')) {
route.abort()
} else {
route.continue()
}
})
}
Freshchat blocking
export async function blockFreshchat(page: Page) {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('freshchat.com') || url.includes('freshworks.com')) {
route.abort()
} else {
route.continue()
}
})
}
Universal chat widget blocker
For maximum coverage, create a universal function that blocks multiple chat widgets simultaneously:
export async function blockAllChatWidgets(page: Page) {
const chatWidgetDomains = [
// Drift
'drift.com', 'driftt.com', 'drift-widget.com',
// Intercom
'intercom.io', 'intercom.com',
// Zendesk
'zendesk.com', 'zdassets.com',
// HubSpot
'hubspot.com', 'hs-scripts.com',
// Crisp
'crisp.chat', 'crisp.help',
// LiveChat
'livechatinc.com', 'livechat.com',
// Freshchat
'freshchat.com', 'freshworks.com',
// Tawk.to
'tawk.to',
// Olark
'olark.com',
// Tidio
'tidio.co',
// Smartsupp
'smartsupp.com'
]
await page.route('**/*', route => {
const url = route.request().url()
const shouldBlock = chatWidgetDomains.some(domain => url.includes(domain))
if (shouldBlock) {
console.log(`Blocked chat widget request: ${url}`)
route.abort()
} else {
route.continue()
}
})
}
Alternative blocking methods
Sometimes request interception isn't enough. Here are additional techniques for stubborn widgets.
CSS-based hiding
Hide widgets using CSS injection:
export async function hideChatWidgets(page: Page) {
await page.addStyleTag({
content: `
/* Hide common chat widget selectors */
#drift-widget,
.intercom-launcher,
#zendesk_widget,
.crisp-client,
#livechat-widget,
.freshchat-widget {
display: none !important;
visibility: hidden !important;
}
`
})
}
JavaScript-based blocking
Prevent widgets from initializing with JavaScript:
export async function preventChatWidgetInit(page: Page) {
await page.addInitScript(() => {
// Block common chat widget globals
window.drift = undefined
window.Intercom = undefined
window.zE = undefined
window.$crisp = undefined
})
}
Combined approach
Use multiple blocking methods for maximum effectiveness:
export async function comprehensiveChatBlock(page: Page) {
// Block network requests
await blockAllChatWidgets(page)
// Hide with CSS
await hideChatWidgets(page)
// Prevent JavaScript initialization
await preventChatWidgetInit(page)
}
Best practices for chat widget blocking
Implementing chat widget blocking effectively requires following proven practices that ensure reliable test execution.
Environment-specific blocking
Only block widgets in test environments:
export async function conditionalChatBlock(page: Page) {
if (process.env.NODE_ENV === 'test' || process.env.CI) {
await blockAllChatWidgets(page)
}
}
Selective blocking
Block widgets only for specific test suites:
// In tests that need widget blocking
test.describe('Checkout flow tests', () => {
test.beforeEach(async ({ page }) => {
await blockDrift(page)
})
// Tests that require clean UI
})
// In tests where widgets are acceptable
test.describe('General navigation tests', () => {
// No widget blocking needed
})
Performance monitoring
Track the impact of widget blocking on test performance:
export async function blockWithMetrics(page: Page) {
const startTime = Date.now()
let blockedRequests = 0
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('drift.com')) {
blockedRequests++
route.abort()
} else {
route.continue()
}
})
console.log(`Blocked ${blockedRequests} widget requests in ${Date.now() - startTime}ms`)
}
Error handling
Gracefully handle blocking failures:
export async function safeBlockChatWidgets(page: Page) {
try {
await page.route('**/*', route => {
const url = route.request().url()
if (url.includes('drift.com')) {
route.abort()
} else {
route.continue()
}
})
} catch (error) {
console.warn('Failed to block chat widgets:', error)
// Continue with test execution
}
}
Testing your blocking implementation
Verify that your chat widget blocking works correctly with these validation techniques.
Visual verification
Check that widgets don't appear in screenshots:
test('verify no chat widgets appear', async ({ page }) => {
await blockDrift(page)
await page.goto('https://your-website.com')
// Take screenshot and verify no widget elements
await expect(page).toHaveScreenshot('page-without-widgets.png')
// Verify specific widget elements don't exist
await expect(page.locator('#drift-widget')).not.toBeVisible()
})
Network request verification
Confirm that widget requests are actually blocked:
test('verify widget requests are blocked', async ({ page }) => {
const blockedUrls: string[] = []
page.on('requestfailed', request => {
if (request.url().includes('drift.com')) {
blockedUrls.push(request.url())
}
})
await blockDrift(page)
await page.goto('https://your-website.com')
expect(blockedUrls.length).toBeGreaterThan(0)
})
Performance impact testing
Measure the performance improvement from blocking widgets:
test('measure performance improvement', async ({ page }) => {
// Test without blocking
const startWithWidgets = Date.now()
await page.goto('https://your-website.com')
const timeWithWidgets = Date.now() - startWithWidgets
// Test with blocking
await page.goto('about:blank')
await blockDrift(page)
const startWithoutWidgets = Date.now()
await page.goto('https://your-website.com')
const timeWithoutWidgets = Date.now() - startWithoutWidgets
console.log(`Performance improvement: ${timeWithWidgets - timeWithoutWidgets}ms`)
})
Conclusion
Chat widgets shouldn't break your automated tests. With the blocking techniques covered in this guide, you can eliminate widget interference and create more reliable test suites.
Key takeaways for implementing chat widget blocking:
- Use request interception to block widget domains at the network level
- Implement environment-specific blocking to avoid affecting production
- Combine multiple blocking methods for stubborn widgets
- Test your blocking implementation to ensure it works correctly
The examples provided work for Drift, Intercom, Zendesk, HubSpot, and many other popular chat platforms.
Start with the universal blocker function and customize it based on your specific widget requirements.
Your Playwright tests will run more consistently, and you'll spend less time debugging flaky test failures caused by chat widget interference.
For comprehensive monitoring of your applications and services, consider using Hyperping to track uptime and performance alongside your automated testing strategy.
