Run Web Automation Tests at Scale with Agentic AI

Generate tests with AI agents or automate test suites with Selenium, Playwright and other frameworks. Run tests in parallel on TestMu AI scalable cloud grid.

Testing

Top-Down Integration Testing Explained With a Real Example

Top-down integration testing tests high-level modules first, with stubs in place of lower ones. See the steps, depth vs breadth order, and a real stubbed run.

Last Updated on:

A product page is ready to test, but the cart service behind its Add to Cart button is still being built, and so is the mini cart that lists what a shopper has picked. Top-down integration testing lets you test the page anyway: you test the highest-level module first, put a stub with fixed responses in place of each unfinished module below it, and swap the stubs for real modules one at a time.

The example in this guide does that on a real store page. Playwright stubs stand in for the store's cart service and mini cart, the checks on the page stay the same in every iteration, and the suite runs on Chrome in the TestMu AI cloud grid.

Overview

Top-down integration testing is an incremental integration testing approach that tests the highest-level module first and replaces the lower-level modules it calls with stubs, which return fixed responses. The stubs are then swapped for the real modules one at a time, with every earlier test rerun after each swap.

What Does It Need?

  • Stubs: Required. Each stub stands in for a lower-level module that the module under test calls and returns predefined responses, so the higher module can run before its dependencies are built.
  • Drivers: Usually not required, because testing starts at the top module, which is already the caller. Drivers stand in for missing callers, which is why bottom-up integration testing relies on them instead.

In What Order Are Modules Integrated?

  • Depth-first integration: Finishes one branch of the module tree, down to its lowest module, before starting the next branch, so one complete path through the system works early.
  • Breadth-first integration: Adds every module on one level before moving down a level, so all interfaces between the upper levels are exercised before any deep module is added.

How Does It Work in a Web App?

In top-down integration testing of a web app, the page is the top module and the server endpoints it calls are the lower modules. Playwright's route.fulfill() stubs each endpoint, and the same checks run on TestMu AI's cloud browsers as each stub is removed.

What Is Top-Down Integration Testing?

Top-down integration testing builds and tests a system from the top of its control hierarchy downward, starting with the main control module that calls the others. That module is tested first while stubs replace the modules it calls, and each stub is then replaced by its real module and retested until the lowest level is integrated.

Big bang integration skips the increments and combines every module before testing anything. Top-down is one of the incremental strategies, which add modules one at a time; the others are bottom-up and sandwich (hybrid) testing, and the guide to incremental testing compares them.

Module tree with Module 1 at the top, Modules 2 and 3 on level 2, and Modules 4 to 7 on level 3, listing the depth-first and breadth-first integration orders

In this tree, Module 1 is the main control module. It calls Modules 2 and 3, which call the modules on level 3, so top-down testing begins with Module 1 and stubs in place of Modules 2 and 3. Which real module replaces a stub next depends on the integration order, depth-first or breadth-first.

Integration tests sit in the middle layer of the test pyramid, above unit tests and below end-to-end tests. Louise J Gibbs' sketchnote summarizes what that layer checks, such as incompatible interfaces and data mismatches between components.

Test pyramid sketchnote on the integration tests level

By Louise J Gibbs

Stubs vs Drivers in Integration Testing

The ISTQB glossary defines a stub as "a type of test double providing predefined responses" and a driver as "a component or tool that temporarily replaces another component and controls or calls a test item in isolation."

In integration testing, a stub stands in for a module that gets called, and a driver stands in for the module that does the calling.

StubDriver
ReplacesA lower-level module that the module under test callsA higher-level module that calls the module under test
Used inTop-down integration testing and sandwich testingBottom-up integration testing and sandwich testing
What it doesReturns predefined responses to the calls it receivesSends inputs to the module under test and checks what comes back
Example in this guideA fake cart service that always answers "1 item(s) - $123.20"A script that posts a product ID to the cart service and checks the JSON reply

Top-down testing rarely needs drivers, because the module under test is already the top caller and the test runner calls it directly. Stubs are not exclusive to top-down testing, though: unit tests use them as well, and both appear in a test harness.

Martin Fowler's test double entry, which credits the vocabulary to Gerard Meszaros, says stubs provide canned answers to calls made during the test, "usually not responding at all to anything outside what's programmed in for the test."

That is a stub's built-in limit: a stub that is wrong about the real module makes the module above it look correct, and the stub variants in the example show what that looks like.

How Top-Down Integration Testing Works

Top-down testing follows the call hierarchy from the entry point down, and each iteration adds exactly one real module. When a test fails in an iteration, the interface that was just added is the first suspect.

  • Map the call hierarchy - start from the entry point (a page, an API controller, a main function) and draw which module calls which. The tree sets the integration order and shows which modules need stubs first.
  • Write one stub per called module - give each stub the same response shape as the real module, including every field the caller reads. A stub that drops a field can make the caller break without raising any error.
  • Test the top module against the stubs - check its control flow, its decisions, and what it shows the user, including error paths that are hard to trigger with a real module, such as a server error.
  • Replace one stub with the real module - swap a single stub, then rerun. With one change per iteration, a new failure belongs to one interface.
  • Rerun every earlier test after each swap - this regression testing pass confirms that the new module did not break a flow that passed in the previous iteration.

Depth-First Integration

Depth-first integration finishes one branch, down to its lowest module, before starting the next. For the module tree above:

  • Iteration 1 - Module 1 is real; stubs replace Modules 2 and 3.
  • Iteration 2 - Module 2 joins; stubs replace Modules 3, 4, and 5.
  • Iteration 3 - Module 4 joins; stubs replace Modules 3 and 5.
  • Iteration 4 - Module 5 joins, which completes the first branch; a stub still replaces Module 3.
  • Iteration 5 - Module 3 joins; stubs replace Modules 6 and 7.
  • Iteration 6 - Module 6 joins; a stub replaces Module 7.
  • Iteration 7 - Module 7 joins, and no stubs remain.

Breadth-First Integration

Breadth-first integration adds every module on one level before moving down a level. For the same tree:

  • Iteration 1 - Module 1 is real; stubs replace Modules 2 and 3.
  • Iteration 2 - Module 2 joins; stubs replace Modules 3, 4, and 5.
  • Iteration 3 - Module 3 joins, which completes level 2; stubs replace Modules 4 to 7.
  • Iteration 4 - Module 4 joins; stubs replace Modules 5, 6, and 7.
  • Iteration 5 - Module 5 joins; stubs replace Modules 6 and 7.
  • Iteration 6 - Module 6 joins; a stub replaces Module 7.
  • Iteration 7 - Module 7 joins, and no stubs remain.

Both orders end with the same integrated system and rerun every earlier test in each iteration; they differ in which defects surface first. Depth-first exposes problems along one full path, such as a single feature from screen to database, while breadth-first exposes problems in the interfaces of the upper levels.

Note

Note: The example in the next section runs on TestMu AI's cloud grid and reads LT_USERNAME and LT_ACCESS_KEY from your environment. A free account gives you both. Sign up for TestMu AI free

Top-Down Integration Testing Example With Playwright

The example tests the Ecommerce Playground, a dummy store built for web automation testing.

One click on the Add to Cart button of its product page calls two server endpoints, so the page is the top module and the endpoints are the lower modules:

ModuleWhat it doesIteration 1Iteration 2Iteration 3
Product page (Add to Cart button and its script)Posts the cart request, shows the confirmation, updates the header cart count, and reloads the mini cartRealRealReal
Cart service (POST index.php?route=checkout/cart/add)Adds the item and returns JSON with success, total, and toast fieldsStubRealReal
Mini cart (GET index.php?route=extension/mz_widget/total/reload)Returns the HTML of the cart summaryStubStubReal

I recorded the page's network traffic on the TestMu AI grid to find those two calls, then read the page's script to see what it takes from the cart service. The confirmation comes from the toast field, and the header cart count is parsed out of total, which must follow the pattern "N item(s) - price".

The spec stubs each endpoint with Playwright's route.fulfill(), which answers an intercepted request with a response you define, so no request reaches the server.

The cloud fixture, lambdatest-setup.ts, is the template from the TestMu AI Playwright skill, copied unchanged: it opens each test's browser on the grid and reports pass or fail to the session. The config declares one cloud project, and the fixture reads the browser, version, and platform from its name:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 60_000,
  fullyParallel: true,
  reporter: 'list',
  projects: [
    { name: 'chrome:latest:Windows 11@lambdatest',
      use: { viewport: { width: 1280, height: 720 } } },
  ],
});

Part 1 defines the two endpoints and the cart service stub. The stub returns the same fields as the real response, and its message ends in [stub] so screenshots show which iteration ran:

// add-to-cart.spec.ts, part 1 of 4: targets and the cart service stub
import type { Page } from '@playwright/test';
import { test, expect } from '../lambdatest-setup';

const PRODUCT = 'https://ecommerce-playground.lambdatest.io/'
  + 'index.php?route=product/product&product_id=40';
const CART = '**/index.php?route=checkout/cart/add';
const MINI_CART = '**/index.php?route=extension/mz_widget/total/reload*';

const stubCart = (page: Page) => page.route(CART, (route) =>
  route.fulfill({ json: {
    success: 'stub',
    total: '1 item(s) - $123.20',
    toast: '<div class="toast" role="alert" data-delay="10000">'
      + '<div class="toast-body">Success: You have added iPhone'
      + ' to your shopping cart! [stub]</div></div>',
  } }));

Part 2 stubs the mini cart and holds the checks every iteration runs: the confirmation text, the header cart count, and the mini cart contents.

// add-to-cart.spec.ts, part 2 of 4: mini cart stub and shared checks
const stubMiniCart = (page: Page) => page.route(MINI_CART, (route) =>
  route.fulfill({ contentType: 'text/html',
    body: '<table><tr><td>iPhone</td><td>x 1</td></tr></table>' }));

async function addToCartAndCheck(page: Page) {
  await page.goto(PRODUCT);
  await page.locator('button.button-cart:visible').first().click();
  await expect(page.locator('#notification-box-top'))
    .toContainText('Success: You have added iPhone');
  await expect(page.locator('.cart-icon .cart-item-total').first())
    .toHaveText('1');
  await expect(page.locator('.widget-total').first())
    .toContainText('iPhone');
}

Part 3 is the integration order. Iteration 1 stubs both lower modules, iteration 2 removes the cart service stub, and iteration 3 removes the last stub, while the checks stay identical:

// add-to-cart.spec.ts, part 3 of 4: one test per integration iteration
test('iteration 1: cart and mini cart stubbed', async ({ page }) => {
  await stubCart(page);
  await stubMiniCart(page);
  await addToCartAndCheck(page);
});

test('iteration 2: real cart, mini cart stubbed', async ({ page }) => {
  await stubMiniCart(page);
  await addToCartAndCheck(page);
});

test('iteration 3: fully integrated', async ({ page }) => {
  await addToCartAndCheck(page);
});

Part 4 uses a stub to produce a server error on demand, which is hard to get from the real cart service, and asserts how the top module handles it:

// add-to-cart.spec.ts, part 4 of 4: a server error, on demand
test('stubbed failure: cart returns HTTP 500', async ({ page }) => {
  await page.route(CART, (route) =>
    route.fulfill({ status: 500, body: 'cart service down' }));
  await page.goto(PRODUCT);
  const dialog = page.waitForEvent('dialog');
  await page.locator('button.button-cart:visible').first().click();
  const alert = await dialog;
  expect(alert.message()).toContain('Internal Server Error');
  await alert.dismiss();
});

I installed Playwright 1.64.0 and ran the spec against Chrome on Windows 11 in the TestMu AI cloud grid with these commands:

npm init -y
npm i -D @playwright/test@1.64.0 playwright@1.64.0
# copy lambdatest-setup.ts from the Playwright skill into the project root
export LT_USERNAME="your-username" LT_ACCESS_KEY="your-access-key"
npx playwright test --project="chrome:latest:Windows 11@lambdatest"

All four tests passed in each of three consecutive runs. This is the output of the third run:

Running 4 tests using 4 workers


  ok 3 [chrome:latest:Windows 11@lambdatest] › tests\add-to-cart.spec.ts:52:5 › stubbed failure: cart returns HTTP 500 (20.7s)
  ok 4 [chrome:latest:Windows 11@lambdatest] › tests\add-to-cart.spec.ts:36:5 › iteration 1: cart and mini cart stubbed (20.8s)
  ok 1 [chrome:latest:Windows 11@lambdatest] › tests\add-to-cart.spec.ts:42:5 › iteration 2: real cart, mini cart stubbed (24.0s)
  ok 2 [chrome:latest:Windows 11@lambdatest] › tests\add-to-cart.spec.ts:47:5 › iteration 3: fully integrated (24.8s)

  4 passed (29.1s)

Each test opened its own cloud session, so the four ran in parallel, and the fixture turns on video, network, and console capture for every session. A screenshot from a separate iteration 1 run on the grid, with both lower modules stubbed, shows the page rendering the stub's confirmation, [stub] marker included:

Ecommerce Playground product page showing the stubbed confirmation: Success: You have added iPhone to your shopping cart! [stub]

Stub Variant Results

Before settling on the final stub, I ran variants of it against the same page on the TestMu AI grid, each changing one detail of the cart service response:

Stub responseWhat the product page did
Every field the page reads: success, total, and toastShowed the confirmation, set the header cart count to 1, and reloaded the mini cart
The same response without the toast fieldShowed no confirmation, though the header cart count still changed to 1
total sent as "1 item" instead of "1 item(s) - price"Showed the confirmation, but the header cart count stayed at 0
HTTP 500 instead of JSONShowed no confirmation and opened a browser alert with the status text and the response body

Two of those variants broke one visible effect while leaving another intact, so a test that checked a single effect would have passed. That is why the shared checks in part 2 cover the confirmation, the cart count, and the mini cart together, and why the stub copies every field of the real response rather than the minimum one assertion needs.

Let Claude Code write Playwright tests that actually pass.

Playwright

Advantages and Disadvantages of Top-Down Integration Testing

The trade-offs follow from the integration order. The top module is real from iteration 1, so its control flow gets the most test runs, while the lowest modules stay stubbed until the final iterations, so their real behavior is tested least.

Advantages

  • Control flow first - defects in the top module's decisions and interfaces show up in the first iteration, before any lower module exists.
  • No drivers to write - the test runner calls the top module directly, so stubs are the only test doubles you build.
  • An early working skeleton - after the first iterations, the main user flow runs end to end with stubs behind it, which gives stakeholders something to try.
  • Easier fault localization - each iteration adds one real module, so a new failure points to one interface.
  • Error paths on demand - a stub can return failures a real module rarely produces, like the HTTP 500 in part 4 of the example.

Disadvantages

  • Many stubs - every called module needs one, and each has to keep matching the real module's responses as they change.
  • Lower modules are tested last - a defect in a foundational module, such as a payment or authentication service, surfaces late in the cycle.
  • Stubs can hide real behavior - a stub answers only what it was programmed with, so a wrong stub can make the module above it look correct, as two of the stub variants above showed.
  • Low-level results are hard to observe early - until the lower modules are real, their behavior is visible only through what the top module shows.

Top-Down vs Bottom-Up Integration Testing

Bottom-up integration testing runs the other way: it starts with the lowest modules and moves up, with drivers standing in for the callers that are not built yet. Sandwich (hybrid) testing combines both and meets in the middle.

Top-downBottom-upSandwich (hybrid)
Starts withThe top moduleThe lowest modulesBoth ends at once
Test doublesStubsDriversStubs and drivers
Tested firstControl flow, interfaces, and the main user flowUtilities, data access, and calculationsThe top and bottom layers
Tested lastThe lowest modulesThe top module and the user flowThe middle layer
Working skeleton earlyYesNoPartly
Fits whenThe top-level design or user flow is the main risk, or is built firstFoundational modules are the main risk, or are ready firstA large system carries risk at both ends

In a web app, the choice often follows the build order. If the pages and their controllers are ready before the services behind them, as on the product page above, start top-down; if the services and data layer are ready first, start bottom-up and add the pages last.

Austin Siewert

Austin Siewert

Co-Founder, Steadfast Systems

Discovered @TestMu AI yesterday. Best browser testing tool I've found for my use case. Great pricing model for the limited testing I do 👏

3M+ Devs and QAs rely on TestMu AI

Deliver immersive digital experiences with Next-Generation Mobile Apps and Cross Browser Testing Cloud

Best Practices for Top-Down Integration Testing

  • Build stubs from real responses - copy the JSON or HTML the real module returns, or record it once and replay it from a HAR file, as the guide to mocking APIs in Playwright shows, so field names and formats match production.
  • Change stubs with the contract - when a lower module's response format changes, update its stub in the same change, or the top-module tests keep passing against a format production no longer sends.
  • Integrate risky low-level modules early - if a foundational module such as payments or authentication is high risk, move it up the integration order instead of leaving it for the last iteration.
  • Mark stubbed runs - add a visible marker to stub responses, like the [stub] suffix in the example, so screenshots, videos, and logs show which iteration produced them.
  • Automate the regression pass - each swap repeats every earlier test, so the suite has to run without manual steps or the reruns get skipped.

Those reruns also have to be fast. HyperExecute, the TestMu AI test orchestration cloud, runs an existing suite from one hyperexecute.yaml file with any framework that has a runnable test command, and its Auto-Split strategy spreads discovered tests across parallel machines. TestMu AI puts it at up to 70% faster than traditional grids, and the HyperExecute getting started guide covers the YAML.

Youtube thumbnail

Conclusion

Start top-down integration testing with the top module you can already run: write one stub per module it calls, copy each stub's response from the real module, and add the real modules back one per iteration with the whole suite rerun every time. To run the Playwright example on more browsers, TestMu AI's Automation Cloud runs the same spec on 3,000+ browser and OS combinations, and the Playwright setup guide lists the capabilities the fixture sends.

Author

...

Harish Rajora

Blogs: 97

  • Twitter
  • Linkedin

Harish Rajora is a software developer at TestMu AI with over 6 years of hands-on experience in Python and cross-platform application development across Windows, macOS, and Linux. He has authored 800+ technical articles and worked on large-scale projects, including GenAI applications and core engineering features used by millions. Harish has led DevOps initiatives building CI/CD pipelines with Jenkins, AWS, GitLab, and GitHub, and holds an M.Tech in Software Engineering from IIIT Allahabad.

Reviewer

...

Srinivasan Sekar

Reviewer

  • Linkedin

Srinivasan Sekar is Director of Engineering at TestMu AI, where he leads engineering and open-source initiatives behind the Selenium and Appium automation grid and owns TestMu AI's MCP Server. A committer to Appium and a contributor to Selenium, WebdriverIO, Taiko, and AppiumTestDistribution, he brings over 15 years of experience in quality engineering and open-source technologies. He is the author of the Apress book 'The MCP Standard: A Developer's Guide to Building Universal AI Tools with the Model Context Protocol,' a Certified Kubernetes and Cloud Native Associate, and an international conference speaker. Before TestMu AI he spent over eight years at Thoughtworks as a Principal Consultant and Quality Architect. Srinivasan holds a B.Tech in Information Technology from Anna University.

Add to Google preferred sources

Summarise with AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

Top-Down Integration Testing FAQs

Did you find this page helpful?

More Related Learning Hubs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests