Messenger Widget Testing Guide

How to Test Intercom Messenger with Playwright: Complete 2026 Guide

A scenario-by-scenario walkthrough of testing Intercom Messenger with Playwright. Iframe traversal, launcher button rendering, bot auto-replies, conversation threads, custom actions, article suggestions, and the pitfalls that break real messenger test suites.

25K+

Intercom serves over 25,000 businesses globally, with the Messenger widget embedded on hundreds of thousands of web applications handling millions of conversations daily.

Intercom

0+Nested iframes per widget
0Scenarios covered
0sAvg bot reply latency
0%Fewer lines with Assrt

Intercom Messenger Conversation Flow

BrowserYour AppIntercom SDKMessenger IframeIntercom APIPage loadLoad Intercom snippetBoot messenger (app_id + user hash)Config + unread countRender launcher + iframeClick launcher buttonSend user messageBot auto-replyDisplay conversation

1. Why Testing Intercom Messenger Is Harder Than It Looks

Intercom Messenger is a third-party widget that injects itself into your page via a JavaScript snippet, then renders its entire UI inside one or more nested iframes. The launcher button, the conversation panel, and the article viewer each live inside separate iframe boundaries. From Playwright's perspective, every interaction requires crossing those iframe boundaries using frameLocator before you can locate any element. Standard page.getByRole() calls will silently find nothing if you forget to scope them to the correct frame.

The second structural challenge is timing. Intercom boots asynchronously after your page loads. The SDK fetches configuration from Intercom's servers, determines whether the current user qualifies for messenger visibility (based on segments, audience rules, and launcher settings), and only then injects the iframe. There is no DOM event your test can listen for to know the widget is ready. You must poll for the iframe's existence or wait for the launcher button to appear inside it.

Third, bot auto-replies arrive on a server-driven schedule. When a user sends a message, the Intercom backend evaluates workflow rules, matches resolution bot paths, and pushes a response back through a WebSocket connection. That response can take anywhere from 500 milliseconds to several seconds. Your tests must wait for the reply to appear in the conversation thread without using brittle fixed-duration sleeps.

Fourth, the Messenger DOM is heavily obfuscated. Class names are hashed and change between Intercom SDK releases. Data attributes are sparse. Intercom uses React internally, so the DOM structure shifts when they refactor components. Your selectors need to target semantic content (text, ARIA roles, structural position) rather than class names.

Fifth, custom actions, article cards, and conversation cards are rendered dynamically based on bot configuration. Your test environment needs a predictable bot workflow configured in Intercom so that sending a specific message always triggers the same response path. Without that, tests become nondeterministic.

Intercom Messenger Iframe Architecture

🌐

Page Load

Your app HTML renders

⚙️

SDK Boot

Intercom snippet loads

↪️

Config Fetch

GET /messenger/web/config

📦

Launcher Iframe

Button injected into DOM

🌐

Click Launcher

User opens messenger

📦

Messenger Iframe

Conversation panel opens

⚙️

WebSocket

Real-time message delivery

2. Setting Up a Reliable Test Environment

Intercom provides separate workspaces for development and production. Always test against a dedicated development workspace to avoid polluting production analytics and conversation data. You will need your workspace's app_id, and if you use Identity Verification (which you should in production), you will also need the HMAC secret to generate user hashes.

Environment Variables

.env.test

Intercom Snippet Integration

Your application should already include the Intercom snippet. For tests, ensure the snippet boots with identity verification enabled. The HMAC hash is computed server-side using the user's email and your HMAC secret. Here is a minimal setup:

intercom-boot.ts

Playwright Configuration

Intercom loads scripts from widget.intercom.io and js.intercomcdn.com. Your Playwright config must not block these domains. Also, set a generous default timeout for iframe operations since the messenger can take several seconds to boot on slow networks.

playwright.config.ts
Install dependencies

Helper: Wait for Intercom Messenger Frame

The single most reused utility in any Intercom test suite is a function that waits for the messenger iframe to exist and returns a scoped frame locator. Intercom injects an iframe with the name intercom-messenger-frame for the main panel, and a separate frame for the launcher button. Here is a robust helper:

tests/intercom/helpers.ts

Test Environment Setup Flow

⚙️

Dev Workspace

Create in Intercom

🔒

Identity Verification

Enable HMAC

Bot Workflow

Configure test bot

⚙️

Env Variables

app_id + HMAC secret

🌐

Playwright Config

Allow Intercom domains

Helpers

Frame locator utilities

1

Launcher Button Renders and Opens

Straightforward

3. Scenario: Launcher Button Renders and Opens

Goal: Verify the Intercom launcher button appears on the page and clicking it opens the messenger panel.

Preconditions: The Intercom snippet is loaded with a valid app_id. The current user matches the audience rules for messenger visibility.

Playwright Implementation

tests/intercom/launcher.spec.ts

What to Assert Beyond the UI

Check that the Intercom boot request completed successfully by monitoring network traffic. The SDK sends a POST to https://api-iam.intercom.io/messenger/web/ping on boot. Verify the response status is 200 and the response body contains the expected app configuration. You can also assert that window.Intercom is defined and callable on the page by evaluating typeof window.Intercom === 'function'.

2

Sending a Message and Receiving a Bot Reply

Moderate

4. Scenario: Sending a Message and Receiving a Bot Reply

Goal: Send a user message through the Intercom Messenger and verify the resolution bot delivers an automated reply.

Preconditions:A resolution bot workflow is configured in your development workspace that triggers on all inbound conversations. The bot should send at least one auto-reply message, such as “Thanks for reaching out! Let me find the right answer for you.”

Playwright Implementation

tests/intercom/send-message.spec.ts

What to Assert Beyond the UI

Intercept the WebSocket frames to confirm the message was delivered to the Intercom backend. You can use page.on('websocket') to capture outgoing frames and verify the message payload includes the correct conversation ID and body text. This catches silent delivery failures where the UI appears to send but the backend never receives the message.

Test run output

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started
3

Navigating a Conversation Thread

Moderate

5. Scenario: Navigating a Conversation Thread

Goal: Open an existing conversation from the conversation list, scroll through the message history, and verify messages render in chronological order.

Preconditions: The test user has at least one previous conversation in their Intercom history. You can create this in a beforeAll hook by sending a message via the Intercom REST API using the POST /conversations endpoint.

Playwright Implementation

tests/intercom/conversation-thread.spec.ts

What to Assert Beyond the UI

Verify the Intercom API call that fetches conversation history returns a 200 status. The SDK requests /messenger/web/conversations/:id when a thread is opened. Intercept this request and validate that the response payload includes the expected number of message parts.

Conversation thread navigation

const messenger = await openMessenger(page);
const previousConversations = messenger.getByText(
  /previous conversations|see all/i
);
if (await previousConversations.isVisible()) {
  await previousConversations.click();
}
const conversationItem = messenger
  .locator('[data-testid="conversation-list-item"]')
  .first();
await expect(conversationItem).toBeVisible({ timeout: 8_000 });
await conversationItem.click();
const messageList = messenger.locator(
  '[data-testid="conversation-container"]'
);
await expect(messageList).toBeVisible();
await expect(
  messenger.getByRole('textbox', { name: /write a reply/i })
).toBeVisible();
69% fewer lines
4

Custom Bot Flow with Buttons and Branching

Complex

6. Scenario: Custom Bot Flow with Buttons and Branching

Goal:Trigger a custom bot workflow, click through button options in the conversation, and verify the bot follows the correct branching path based on the user's selection.

Preconditions:Your Intercom workspace has a custom bot configured with at least two branching paths. For example, a “How can I help?” bot that offers “Billing”, “Technical Support”, and “Other” as button options, each leading to a different follow-up message.

Playwright Implementation

tests/intercom/custom-bot.spec.ts

What to Assert Beyond the UI

Monitor the API calls to verify the button click is registered as a “quick reply” in the conversation payload. Intercom records button clicks as a specific message part type. Assert the request body includes the correct reply_option value corresponding to the button the user clicked. This ensures the branching logic will work correctly even when the button label text changes.

5

Article Suggestions and Help Center Search

Moderate

7. Scenario: Article Suggestions and Help Center Search

Goal: Trigger an article suggestion from the bot, click through to the article viewer within the messenger, and verify the article content renders correctly. Also test the help center search functionality.

Preconditions: Your Intercom workspace has at least one published help center collection with articles. A resolution bot workflow is configured to suggest articles when it matches certain keywords.

Playwright Implementation

tests/intercom/article-suggestions.spec.ts

What to Assert Beyond the UI

Verify that the article fetch request to /messenger/web/articles/:id returns a 200 status and that the article body HTML is not empty. For search, intercept the /messenger/web/search/articles request and confirm the query parameter matches what the user typed and that the response includes at least one result object.

Article suggestion and reading

const messenger = await openMessenger(page);
const composer = messenger.getByRole('textbox', {
  name: /write a reply|type a message/i,
});
await composer.fill('How do I reset my password?');
await composer.press('Enter');
const articleCard = messenger.locator(
  '[data-testid="article-card"]'
).first();
await expect(articleCard).toBeVisible({ timeout: 12_000 });
await articleCard.click();
const articleViewer = messenger.locator(
  '[data-testid="article-viewer"]'
);
await expect(articleViewer).toBeVisible({ timeout: 8_000 });
const articleText = await articleViewer.textContent();
expect(articleText!.length).toBeGreaterThan(50);
71% fewer lines
6

Custom Actions and Launcher Visibility Rules

Complex

8. Scenario: Custom Actions and Launcher Visibility Rules

Goal: Test that the Intercom launcher respects visibility rules (showing or hiding based on page URL, user segment, or custom data attributes) and that custom actions triggered via the Intercom JavaScript API work correctly.

Preconditions: Your workspace has launcher visibility rules configured. For example, the messenger is hidden on the /pricing page but visible on /dashboard. You also have a custom action that opens the messenger to a specific conversation or bot workflow when called programmatically.

Playwright Implementation

tests/intercom/custom-actions.spec.ts

What to Assert Beyond the UI

For visibility rules, assert that the Intercom boot config response includes the correct launcher_enabled value for each page. Intercept /messenger/web/ping and inspect the response JSON. For custom actions, verify the JavaScript API call resolves without errors by wrapping it in a try/catch inside page.evaluate and checking the return value.

Running visibility and custom action tests

9. Common Pitfalls That Break Intercom Messenger Tests

These pitfalls come from real-world Intercom test suites, GitHub issues, and community forum posts. Every one of them has caused flaky or permanently broken tests in production CI pipelines.

Pitfall 1: Forgetting to Use frameLocator

The most common mistake is using page.getByRole() directly to find elements inside the Intercom widget. Since the widget renders inside an iframe, these locators will time out with no useful error message. You must always scope your locators through page.frameLocator('iframe[name="intercom-messenger-frame"]') first. This is the single most reported issue in Playwright forum threads about Intercom testing.

Pitfall 2: Using Fixed Timeouts Instead of Waiting for Elements

Intercom's boot time varies based on network latency, workspace configuration complexity, and whether the CDN cache is warm. Tests that use await page.waitForTimeout(3000) before interacting with the messenger will either be too slow (wasting CI time) or too fast (failing intermittently). Always wait for the actual iframe element to appear in the DOM using waitForSelector.

Pitfall 3: Class Name Selectors Break on SDK Updates

Intercom's class names are generated by their build process and change between SDK versions. Selectors like .intercom-1x2y3z will break silently when Intercom deploys a new version of the widget (which happens frequently, since the SDK is loaded from their CDN). Use ARIA roles, text content, and data-testid attributes instead.

Pitfall 4: Bot Replies Depend on Workspace Configuration

If someone modifies the resolution bot workflow in your development workspace, every test that asserts on specific bot reply text will break. Treat your Intercom workspace configuration as infrastructure code. Document the expected bot flows, restrict editing permissions on the development workspace, and version-control your bot workflow exports using the Intercom API.

Pitfall 5: Identity Verification HMAC Mismatches

If your application enables Identity Verification but your test environment computes the HMAC hash with a different secret (or skips it entirely), the messenger will boot in an “unverified” state. Intercom may reject API calls, block conversations, or display a degraded experience. Ensure your test environment uses the same Identity Verification secret as your development workspace.

Pitfall 6: Parallel Tests Create Overlapping Conversations

If multiple test workers share the same test user identity, they will see each other's conversations in the messenger. This causes assertions on message count or conversation list order to become nondeterministic. Use unique user identities per test worker by appending the worker index to the email address: test+worker${workerIndex}@yourcompany.com.

Pre-flight Checklist Before Running Intercom Tests

  • Use frameLocator for all messenger element access
  • Wait for iframe attachment, not fixed timeouts
  • Target ARIA roles and text, not class names
  • Verify bot workflow configuration is stable
  • Match Identity Verification HMAC secret
  • Use unique user identities per parallel worker
  • Use fixed timeouts like waitForTimeout(3000)
  • Select elements by Intercom class names
  • Share one test user across parallel workers

10. Writing These Scenarios in Plain English with Assrt

Every scenario above requires you to know Playwright's frameLocator API, understand Intercom's iframe naming conventions, handle variable bot reply timing, and maintain selectors that break when Intercom updates their SDK. Assrt lets you describe what you want to test in plain English and compiles it into the same Playwright TypeScript you saw throughout this guide.

Here is the “send a message and receive a bot reply” scenario from Section 4, rewritten as an Assrt scenario file:

intercom-bot-reply.assrt

Assrt compiles each scenario block into the Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can read, run, and modify. When Intercom renames an iframe, changes their launcher button markup, or restructures the conversation thread DOM, Assrt detects the failure, analyzes the new DOM, and opens a pull request with the updated selectors and frame locators. Your scenario files stay untouched.

Bot reply: Playwright vs Assrt

import { test, expect } from '@playwright/test';
import { openMessenger } from './helpers';

test('sends a message and receives bot reply', async ({ page }) => {
  await page.goto('/');
  const messenger = await openMessenger(page);
  const composer = messenger.getByRole('textbox', {
    name: /write a reply|type a message/i,
  });
  await composer.fill('I need help with billing');
  await composer.press('Enter');
  await expect(
    messenger.getByText('I need help with billing')
  ).toBeVisible();
  const botReply = messenger.locator(
    '[data-testid="operator-message"]'
  ).first();
  await expect(botReply).toBeVisible({ timeout: 10_000 });
  await expect(
    messenger.getByText(/thanks for reaching out|let me find/i)
  ).toBeVisible({ timeout: 10_000 });
});
57% fewer lines

Start with the launcher visibility scenario. Once it is green in your CI, add the message and bot reply test, then the custom bot branching flow, then article suggestions, then custom actions and visibility rules. In a single afternoon you can have complete Intercom Messenger coverage that most applications never manage to achieve by hand. The iframe traversal, bot reply timing, and selector maintenance are all handled for you.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk