TestRelic

Unified Testing (Browser + API)

How do I combine E2E browser testing with API testing in a single TestRelic test, using both the page and request fixtures?

Unified testing combines browser-based E2E testing with API testing in a single test. TestRelic captures both navigation timelines and API call details together, in one report.

Best of both worlds

Unified testing is ideal for scenarios where you need to verify API data is correctly displayed in the UI, or set up data via API before testing UI interactions.

Using both fixtures

Import from @testrelic/playwright-analytics/fixture and use both page and request in the same test:

tests/unified/basic.spec.ts
import { test, expect } from '@testrelic/playwright-analytics/fixture';

test('API data matches UI', { tag: ['@unified'] }, async ({ page, request }) => {
  const apiResponse = await request.get('https://jsonplaceholder.typicode.com/users/1');
  expect(apiResponse.status()).toBe(200);
  const user = await apiResponse.json();

  await page.goto('https://example.com/users/1');
  await expect(page.locator('.user-name')).toHaveText(user.name);
});

Fixture usage

Unlike API-only tests, which require testRelicApiFixture, unified tests use the standard TestRelic fixture — it provides both page and request with full tracking.

Common patterns

API sets up data, UI verifies it:

tests/unified/api-setup-ui-verify.spec.ts
test('create post via API and verify in UI', { tag: ['@unified'] }, async ({ page, request }) => {
  const createResponse = await request.post('https://jsonplaceholder.typicode.com/posts', {
    data: { title: 'Test Post from API', body: 'Created via API', userId: 1 },
  });
  expect(createResponse.status()).toBe(201);

  await page.goto('https://jsonplaceholder.typicode.com/posts');
});

Cross-validate data between API and UI:

tests/unified/cross-validation.spec.ts
test('user data consistency across UI and API', { tag: ['@unified'] }, async ({ page, request }) => {
  const apiResponse = await request.get('https://api.example.com/users/1');
  const apiUserData = await apiResponse.json();

  await page.goto('https://example.com/users/1');
  const uiName = await page.locator('.user-name').textContent();

  expect(uiName).toBe(apiUserData.name);
});

Sequential API → UI → API workflow:

tests/unified/sequential-operations.spec.ts
test('create → search → update', { tag: ['@unified'] }, async ({ page, request }) => {
  const createResponse = await request.post('https://api.example.com/products', {
    data: { name: 'Test Product', price: 99.99 },
  });
  const product = await createResponse.json();

  await page.goto('https://example.com/products');
  await page.fill('input[name="search"]', product.name);
  await expect(page.locator('.product-card', { hasText: product.name })).toBeVisible();

  await request.put(`https://api.example.com/products/${product.id}`, {
    data: { ...product, price: 89.99 },
  });
});

Configuration

playwright.config.ts
export default defineConfig({
  reporter: [
    ['@testrelic/playwright-analytics', {
      outputPath: './test-results/analytics-timeline.json',
      includeStackTrace: true,
      includeCodeSnippets: true,
      includeNetworkStats: true,
      redactPatterns: [/api_key=[^&\s]+/gi, /password=[^&\s]+/gi],
    }],
  ],
  use: {
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
});

Running unified tests

Terminal
npx playwright test --grep @unified

Best practices

  • Prefer API for setup — it's faster than driving the browser to create fixtures.
  • Cross-validate — always compare API data against extracted UI text, don't assume.
  • Use the request fixture, not page.fetch(), for API calls made inside browser tests — it bypasses CORS.
  • Wait after async writes — give the server time to process before reloading the UI to check a change.

Next steps

Was this page helpful?

On this page