WebSocket Mocking in E2E Tests

July 13, 2026 · View on GitHub

The E2E framework includes a local WebSocket server that intercepts production WebSocket connections during tests. This enables deterministic testing of features that use real-time data streams (e.g., account activity notifications).

Architecture

App (E2E build)

  ├─ shim.js rewrites wss://gateway.api.cx.metamask.io → ws://localhost:<port>

  └─ LocalWebSocketServer (tests/websocket/server.ts)

       └─ Protocol mock (e.g. account-activity-mocks.ts)
            └─ Handles subscribe/unsubscribe, sends mock notifications

Key files:

FilePurpose
tests/websocket/server.tsLocalWebSocketServer — generic WS server implementing the Resource interface
tests/websocket/constants.tsService definitions (production URL, fallback port, launch arg key)
tests/websocket/account-activity-mocks.tsProtocol-specific mock for AccountActivity WS
tests/framework/fixtures/FixtureHelper.tsCreates and manages the WS server per test run

How It Works

  1. withFixtures in FixtureHelper.ts creates a LocalWebSocketServer instance for every test run
  2. The server is started via startResourceWithRetry with automatic port allocation
  3. setupAccountActivityMocks() attaches protocol-specific message handlers
  4. The allocated port is passed to the app via launch args (accountActivityWsPort)
  5. The app's E2E shim rewrites the production WebSocket URL to ws://localhost:<port>
  6. After the test, the server is stopped and connections are cleaned up

Using the Existing AccountActivity Mock

The AccountActivity WebSocket mock is available in every test automatically. For most tests, you don't need to do anything — the mock server handles subscribe/unsubscribe handshakes in the background.

To explicitly test WebSocket behavior, use the helpers from account-activity-mocks.ts:

import {
  waitForAccountActivitySubscription,
  getAccountActivitySubscriptionCount,
  waitForAccountActivityDisconnection,
  createBalanceUpdateNotification,
} from '../../websocket/account-activity-mocks';

Waiting for a subscription

// Set up the waiter BEFORE the action that triggers the connection
const subscriptionPromise = waitForAccountActivitySubscription();
await loginToApp();
const subscriptionId = await subscriptionPromise;

Checking subscription count

assertEqual(getAccountActivitySubscriptionCount(), 1);

Waiting for disconnection

await device.sendToHome();
await waitForAccountActivityDisconnection();

Sending a mock balance update

const notification = createBalanceUpdateNotification({
  subscriptionId,
  channel: 'account-activity.v1',
  address: '0x1234...',
  chain: 'eip155:1',
  updates: [
    {
      asset: { fungible: true, type: 'native', unit: 'ETH', decimals: 18 },
      postBalance: { amount: '1000000000000000000' },
      transfers: [
        { from: '0x0...', to: '0x1234...', amount: '500000000000000000' },
      ],
    },
  ],
});

// Broadcast to all connected clients
accountActivityWsServer.sendMessage(JSON.stringify(notification));

Example spec

See tests/smoke-appium/account-activity/web-socket-connection.spec.ts for a complete example with three tests covering subscribe-on-login, resubscribe-after-background, and resubscribe-after-lock.

Adding a New WebSocket Service Mock

To mock a new WebSocket service (e.g., a new real-time data feed):

1. Define the service in tests/websocket/constants.ts

export const MY_NEW_WS: WebSocketServiceConfig = {
  url: 'wss://my-service.example.com',
  fallbackPort: 8090, // pick a unique port
  launchArgKey: 'myNewWsPort',
};

// Add it to the services array
export const WS_SERVICES: WebSocketServiceConfig[] = [
  ACCOUNT_ACTIVITY_WS,
  MY_NEW_WS,
];

2. Create a protocol mock in tests/websocket/

// tests/websocket/my-service-mocks.ts
import type LocalWebSocketServer from './server.ts';
import { WebSocket } from 'ws';

export async function setupMyServiceMocks(
  server: LocalWebSocketServer,
): Promise<void> {
  const wsServer = server.getServer();

  wsServer.on('connection', (socket: WebSocket) => {
    // Handle incoming messages
    socket.on('message', (data) => {
      const raw = data.toString();
      // Parse and respond based on your protocol
    });

    // Send initial data on connection
    socket.send(JSON.stringify({ type: 'connected' }));
  });
}

3. Register in FixtureHelper.ts

Add the server creation, startup, and cleanup alongside the existing accountActivityWsServer pattern:

const myNewWsServer = new LocalWebSocketServer('myNewService');
// ...
await startResourceWithRetry(ResourceType.MY_NEW_WS, myNewWsServer);
await setupMyServiceMocks(myNewWsServer);

Pass the port via launch args:

[MY_NEW_WS.launchArgKey]: isAndroid
  ? `${MY_NEW_WS.fallbackPort}`
  : `${myNewWsServer.getServerPort()}`,

4. Register the port in PortManager

Add the new ResourceType and fallback port in tests/framework/PortManager.ts so the framework can manage port allocation and Android adb reverse forwarding.