ScreenshotNeo

BlogEngineering

Are Automated UI Tests Slow? Common Causes and Fixes

Slow UI tests often come from waits, browser and network overhead, long journeys, or limited CI capacity. Diagnose the slow phase before changing the suite.

By the ScreenshotNeo team4 October 20268 min read

Automated UI tests can be slow because they wait for the wrong signal, repeat long browser journeys, depend on slow servers or third-party resources, or run with too little safe parallel capacity. First measure where each test spends its time. Then fix the slow phase while preserving the behavior the test is meant to verify.

A browser test’s elapsed time is not a reliable measure of application performance by itself. Browser startup, network variation, server response, third-party assets, and automation instrumentation all contribute. Selenium generally advises against using WebDriver for performance testing; use performance-focused measurements for application speed instead. Selenium’s performance testing guidance

1. Diagnose the slow phase first

Break elapsed time into observable phases rather than guessing from the suite’s total duration. Compare the same test locally and in CI, and look for recurring delays across runs. The proportions vary by application and environment; there is no universal best worker count or runtime threshold.

Phase What to inspect Possible signal
Setup and browser launch Fixture setup, browser startup, WebDriver or framework logs Time is spent before navigation begins
Navigation Navigation timing, server logs, browser trace, network requests A document or a required resource responds slowly
UI transition Trace, selector state, application and API logs The page loaded but the needed state appears later
Assertion Assertion retries and timeout details An assertion repeatedly polls or waits close to its timeout
Teardown Cleanup hooks, server shutdown, data deletion Time accumulates after the checks finish
  1. Capture timings for the slow tests, including setup, navigation, action, assertion, and teardown where your framework allows.
  2. Use a browser trace, test logs, and network evidence to identify the phase that dominates. Playwright recommends trace collection for CI diagnosis. Playwright best practices
  3. Repeat the observation enough to distinguish a persistent bottleneck from an occasional slow request. Compare like environments and avoid treating one run as a benchmark.
  4. Change one cause at a time, then confirm that the test still checks the intended behavior and that its runtime changed in the expected phase.

2. Replace fixed sleeps with waits for the needed state

Navigation reaching a document readiness state does not guarantee that a JavaScript application has rendered the state needed for the next action. Hydration, asynchronous data, and client-side transitions can continue afterward. A fixed sleep delays every run by the same amount, even when the page is ready sooner, and can still be too short on a slower run.

Wait for the condition the next action requires: a button becoming enabled, a result appearing, a loading indicator disappearing, or a particular value being shown. Selenium documents explicit waits; Playwright actions check actionability and its assertions retry until the expected state or a timeout. Avoid layering generic waits on top of framework auto-waiting without evidence. Selenium waiting strategies · Playwright actionability

Playwright example

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

test('shows search results', async ({ page }) => {
  await page.goto('http://127.0.0.1:3000');
  await page.getByRole('textbox', { name: 'Search' }).fill('invoice');
  await page.getByRole('button', { name: 'Search' }).click();
  await expect(page.getByRole('heading', { name: 'Search results' })).toBeVisible();
});

Selenium Python example

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

with webdriver.Chrome() as driver:
    driver.get('http://127.0.0.1:3000')
    wait = WebDriverWait(driver, 10)
    search = wait.until(EC.visibility_of_element_located((By.NAME, 'q')))
    search.send_keys('invoice')
    driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
    wait.until(EC.visibility_of_element_located((By.ID, 'search-results')))

Use a timeout that gives the condition a reasonable chance to complete and produces a useful failure when it does not. A timeout is a ceiling, not a delay to add to every test. If a short fixed sleep makes a race disappear during diagnosis, treat that as evidence of a synchronization problem and replace it with a state-based wait. Selenium describes this diagnostic tactic in its troubleshooting guidance.

3. Keep browser journeys focused

End-to-end tests are valuable for checking important user-visible paths. They become expensive when they repeat setup and navigation that do not add confidence to the behavior being tested. Keep browser coverage focused on meaningful user workflows; use controlled setup or lower-level tests for prerequisite work when that preserves the test’s purpose.

Prefer owned, deterministic test pages and data. External pages can change their content, add overlays, or load resources unpredictably. When an external response is not the behavior under test, use the framework’s network controls to provide a deterministic response. Keep separate integration coverage for cases where the real external behavior matters. Playwright best practices

Long spec files can also be harder to diagnose and run reliably. Cypress recommends splitting very long spec files, while noting that there is no single runtime threshold that predicts crashes across applications and hardware. Split around understandable test responsibilities rather than an arbitrary duration target. Cypress FAQ

4. Separate browser, server, network, and instrumentation costs

A slow test may be waiting on browser startup, the application server, an API, a third-party JavaScript or CSS host, or the automation layer itself. Record network requests and server timing where available, and inspect traces before attributing the delay to application code. Cypress also notes that test instrumentation adds overhead. Cypress FAQ

Do not use a functional UI suite’s wall-clock time as a precise application speed measurement. Functional tests optimize for confidence in behavior and may deliberately wait for conditions. Performance tests should measure the target operation with tooling designed for performance analysis, while controlling the environment and accounting for the specific sources of variation identified by Selenium. Selenium performance testing

5. Add parallelism only when tests and infrastructure support it

Parallel workers can reduce wall-clock time when tests are queued and the runner has spare CPU, memory, browser capacity, and backend capacity. They can also increase contention or expose shared-state bugs. Playwright runs workers as separate processes and lets teams set a worker limit; browser isolation does not automatically isolate records in a shared backend. Playwright parallelism

  1. Make each test independent and give it unique records, accounts, or other mutable backend data.
  2. Run a small worker-count comparison on the actual CI runner while watching resource use and failure rate.
  3. Increase workers only while total wall time improves and the suite remains stable.
  4. If higher parallelism makes the suite slower or flaky, reduce contention, isolate state, or lower the worker limit.

Selenium also recommends avoiding shared state between tests. Selenium: avoid sharing state

6. Choose a fix that matches the bottleneck

Observed bottleneck Try Check that
Repeated fixed waits Wait for the specific UI condition; remove redundant waits The condition represents the state required by the next action
Many slow browser journeys Focus end-to-end coverage on important user paths; use deterministic setup where appropriate The revised tests still provide the intended confidence
Slow or variable external resources Control responses when real external behavior is outside test scope Integration behavior is tested where it matters
CI queueing with spare capacity Increase workers gradually CPU, memory, browser, and backend capacity are sufficient
Flaky failures after parallelizing Isolate test data and shared state; reduce workers while fixing collisions Tests pass independently and together
Need to know application speed Use performance-focused measurements Browser automation overhead is not mistaken for application latency

A 2023 research paper reports an 11.1% execution-time reduction for its proposed time-based asynchronous-wait repair method and evaluation. That is a study-specific result, not an expected gain for an arbitrary suite. TRaf research paper abstract

7. Troubleshooting common symptoms

Symptom Likely cause Fix
Test passes locally but times out in CI Different load, server or network latency, resource contention, or a race hidden on a fast machine Inspect CI trace and logs; wait for the required state; check server and network timing rather than simply extending every timeout
Test fails immediately after navigation Document readiness was mistaken for completion of a client-rendered state Wait for the specific element, value, or outcome the test needs
Adding a sleep fixes an intermittent failure The test likely acts before the application reaches the required state Use the sleep as a diagnostic clue, then replace it with a condition-based wait
More workers increase runtime CPU, memory, browser, server, or backend contention Lower worker count and measure resource use; increase capacity or reduce contention only if warranted
Tests fail only when run together Shared records, accounts, or external state are being mutated concurrently Give tests independent data and remove order dependencies
Occasional long navigation Server or third-party resource variability Use network evidence to identify the slow request; control external responses when they are not under test
Suite time is high but individual actions seem quick Repeated setup, browser launches, hooks, or teardown may dominate Profile those phases and reduce redundant work while keeping required cleanup

8. Performance, reliability, and cost considerations

Measure the cost of a remedy across both wall time and reliability. More workers may consume more CI resources without reducing elapsed time if the runner or backend is saturated. Replacing real browser setup with shortcuts may save time but change what the test verifies. Keep a clear boundary between user-flow checks, integration checks, and application performance measurements.

There is no universal optimal timeout, worker count, or suite duration. Tune against the actual CI environment and application. Prefer deterministic inputs, meaningful waits, isolated state, and traces that make the next failure diagnosable. The research sources do not establish a general numerical speedup for these fixes.

9. Or skip the browser setup

For a website screenshot used in visual review, documentation, or an AI workflow, ScreenshotNeo returns a screenshot or PDF from one GET request. It is a screenshot API and MCP server, not a replacement for functional UI tests that exercise application behavior. The API accepts common screenshot parameter names used by other providers. See the ScreenshotNeo API documentation.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

Replace the example URL and supply your API key. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Every feature is available on every plan.

Sign up for 1,000 free screenshots a month, with no card required.

10. Frequently asked questions

Are slow UI tests always a sign that the application is slow?

No. The test also measures browser, network, server, third-party, and automation overhead. Use performance-focused tooling to assess application speed.

Should I increase every timeout?

No. First identify which condition is timing out. A larger timeout can hide a slow or incorrect signal and make failures take longer to report.

How many parallel workers should I use?

There is no universal number. Measure on the target runner, isolate mutable test data, and choose a worker limit that fits its available resources.

Can a screenshot API replace browser tests?

No. A screenshot can support visual review or capture workflows, but it does not establish that a user interaction or application behavior works. Use functional tests for those checks.