ScreenshotNeo

BlogHow-to

How to Handle AngularJS Modal Dialogs with Selenium

Learn to distinguish AngularJS DOM modals from browser alerts, choose robust locators, wait for animations, click controls, and verify outcomes with Selenium.

By the ScreenshotNeo team1 October 20267 min read

AngularJS applications can display two different things that developers call a “modal”: a native browser alert/confirm/prompt, or a dialog rendered in the page DOM by Angular UI Bootstrap, Bootstrap, or a custom directive. Selenium handles them through different APIs.

For a DOM modal, locate the rendered dialog, wait for the state you need, click its control, and assert the resulting state. For a native browser prompt, use Selenium’s alert API. Always inspect the live markup instead of assuming a universal AngularJS selector.

The examples below use JavaScript with Selenium WebDriver. The same decisions apply to other Selenium bindings.

1. Identify the dialog type first

What you see How it is implemented Selenium approach
Alert, confirm, or prompt owned by the browser JavaScript dialog outside the page DOM driver.switchTo().alert()
Angular UI Bootstrap $uibModal Angular template rendered into the DOM Ordinary element locators and waits
Bootstrap or custom AngularJS modal DOM element, often with a backdrop and CSS classes Ordinary element locators and waits

Selenium documents separate interfaces for browser prompts and page elements. A native prompt cannot be found with CSS, while a DOM modal cannot be handled with the alert API. See Selenium’s alert documentation.

2. Inspect the rendered modal and choose stable locators

Open the application manually, trigger the modal, and inspect the live DOM in browser developer tools. Record:

  • An accessible role such as role="dialog" and its accessible name or heading.
  • A stable application attribute such as data-testid, an ID, or a distinctive class.
  • The actual button text and type (submit, button, or a close control).
  • Whether closing hides the modal or removes it from the DOM.
  • Whether the backdrop, Escape key, or close button is the intended dismissal behavior.

$uibModal creates a dialog, but the final markup comes from your template and Angular UI Bootstrap version. The Angular UI Bootstrap 2.3.2 documentation describes the service configuration; it does not guarantee one selector for every application.

Prefer a locator tied to meaning rather than generated Angular classes:

const modal = driver.findElement(By.css('[role="dialog"][aria-labelledby="edit-title"]'));
const save = modal.findElement(By.css('button[type="submit"]'));

If your application has no stable attributes, add a test-only attribute such as data-testid="edit-modal". This is usually more reliable than depending on nested Bootstrap markup.

3. Complete Selenium JavaScript example for a DOM modal

Install Selenium and run this script against your application:

npm install selenium-webdriver
const { Builder, By, until } = require('selenium-webdriver');

(async function handleAngularModal() {
  const driver = await new Builder().forBrowser('chrome').build();
  try {
    await driver.get('https://your-app.example/orders');

    await driver.findElement(By.css('[data-testid="open-edit"]')).click();

    const modal = driver.findElement(By.css('[data-testid="edit-modal"]'));
    await driver.wait(until.elementIsVisible(modal), 5000);

    const title = await modal.findElement(By.css('h2, [role="heading"]')).getText();
    if (title !== 'Edit order') {
      throw new Error(`Unexpected modal title: ${title}`);
    }

    await modal.findElement(By.css('input[name="quantity"]')).clear();
    await modal.findElement(By.css('input[name="quantity"]')).sendKeys('3');
    await modal.findElement(By.css('button[type="submit"]')).click();

    // Some applications remove the modal; others keep it and set display:none.
    await driver.wait(async () => {
      try {
        return !(await modal.isDisplayed());
      } catch (error) {
        // A removed element is also a successful close.
        return error.name === 'StaleElementReferenceError';
      }
    }, 5000, 'Edit modal did not close');

    await driver.wait(until.elementLocated(By.css('[role="status"]')), 5000);
    const status = await driver.findElement(By.css('[role="status"]')).getText();
    if (!status.includes('saved')) {
      throw new Error(`Save result was not shown: ${status}`);
    }
  } finally {
    await driver.quit();
  }
})();

This example waits for visibility before interacting, scopes controls to the intended dialog, and verifies the application result after clicking Save. Replace selectors and expected text with your application’s rendered markup.

4. Native alert, confirm, and prompt dialogs

A browser alert is not a DOM node. Wait for it, read its text when useful, then accept or dismiss it. For a prompt, send text before accepting.

const { Builder, until } = require('selenium-webdriver');

const driver = await new Builder().forBrowser('chrome').build();
try {
  await driver.get('https://your-app.example/legacy-action');
  await driver.findElement(By.css('[data-testid="delete"]')).click();

  await driver.wait(until.alertIsPresent(), 5000);
  const alert = await driver.switchTo().alert();
  const message = await alert.getText();
  if (!message.includes('Delete')) throw new Error(`Unexpected alert: ${message}`);
  await alert.accept();
} finally {
  await driver.quit();
}

For a confirmation that should be cancelled, call alert.dismiss(). For a prompt, call alert.sendKeys('value') before accept(). Selenium describes this workflow in its JavaScript alerts, prompts and confirmations guide.

5. Wait for the state that matters

AngularJS may render or reveal a modal after the initial document load. Selenium’s explicit waits poll a condition until it succeeds or times out. The Selenium waiting strategies guide covers presence, visibility, clickability, and disappearance.

Presence versus visibility

  • Presence: the node exists in the DOM, even if CSS hides it.
  • Visibility: the node is displayed and can normally be interacted with.
  • Clickability: visibility plus enabled state; an overlay can still intercept the click.
  • Gone: the modal is removed or hidden after the action.

Use until.elementLocated when insertion is the condition, then until.elementIsVisible when the user must see it. If the framework keeps a hidden modal node, wait for isDisplayed() to become false rather than waiting for staleness.

Animations and Bootstrap events

Bootstrap 4.6 fires shown.bs.modal after a modal is visible and its transition finishes, and hidden.bs.modal after hiding completes. Confirm the application’s Bootstrap major version before using those event names; custom Angular directives may use different events. Details are in the Bootstrap 4.6 modal documentation.

If your test page exposes an event flag, wait for it with a JavaScript condition:

await driver.wait(async () => {
  return driver.executeScript(() => window.lastModalEvent === 'shown.bs.modal');
}, 5000, 'Modal transition did not finish');

Otherwise, waiting for visible state and then for the post-click hidden state is sufficient. Avoid arbitrary sleeps: a short sleep races slow rendering, while a long sleep slows every test.

Do not mix implicit and explicit waits

Set either an explicit-wait strategy or a carefully understood implicit timeout. Selenium warns: “Do not mix implicit and explicit waits.” Mixing them can make total timeouts unpredictable.

6. AngularJS-specific diagnosis

AngularJS bindings and watchers run within Angular’s execution context. Code invoked directly by the browser can run outside that context, so a model change may not trigger the normal digest and view update. AngularJS explains this in its bootstrap guide and scope guide.

When a click appears to work but the modal does not update:

  1. Confirm Selenium clicked the intended element and no backdrop intercepted it.
  2. Inspect the DOM for the expected model-driven change.
  3. Check the browser console for handler errors.
  4. Prefer the application’s real click path over injecting scope changes.
  5. Wait for the resulting DOM state rather than assuming a fixed digest duration.

7. Common failures and fixes

Failure Likely cause Fix
NoSuchElementError The modal has not been inserted, or the selector describes assumed markup. Wait for location, inspect live markup, and use a stable attribute.
ElementNotInteractableError The node exists but is hidden or still transitioning. Wait for visibility and the end of the transition.
ElementClickInterceptedError A backdrop, animation layer, or another overlay covers the button. Wait for clickability, target the correct modal, and verify z-index/animation state.
Alert wait times out The “alert” is actually a DOM modal, or the click did not trigger it. Inspect the DOM; use the alert API only for browser prompts.
Modal closes but test fails The test waits for staleness while the app only hides the node. Wait for isDisplayed() to become false, or assert the success message.
Button click has no application effect Handler error, validation failure, or code running outside Angular’s normal context. Check console and validation state, then wait for the resulting message or route.
Intermittent failures in CI Race with network data, lazy rendering, or CSS transitions. Wait on a meaningful application condition; capture browser logs and screenshots on failure.

8. Reliability, performance, and test design

  • Use one explicit timeout policy and make timeout messages identify the expected state.
  • Scope locators to the dialog so a similarly named page button cannot be clicked accidentally.
  • Assert both the action and its result: disappearance alone does not prove a save succeeded.
  • Test each supported dismissal path separately: submit, cancel, close icon, Escape, and backdrop only when those behaviors are requirements.
  • Keep test data deterministic. A validation error can leave the modal open and look like a Selenium timing failure.
  • Reuse a driver only when test isolation remains clear; otherwise a fresh browser per test reduces leaked modal state.
  • For speed, wait for the smallest reliable condition instead of a global sleep. For diagnosis, save a screenshot and browser log when a wait expires.

There is no universal AngularJS modal selector or timing value. The application’s rendered structure, Bootstrap version, network behavior, and animation settings determine the correct condition.

9. Or skip the browser setup

If your goal is to capture a page or modal state rather than drive an end-to-end browser test, ScreenshotNeo provides a website screenshot API and MCP server. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Every response identifies the page verdict and billing status.

One call is enough:

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

Python:

import requests

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

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://your-app.example/orders' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for options such as full-page capture, CSS element capture, custom JavaScript, waits, cookies, headers, device presets, dark mode, PDF output, caching, signed links, asynchronous jobs, webhooks, and bulk capture. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

10. FAQ

Can Selenium click an AngularJS modal with switchTo().alert()?

Only if it is a native browser alert, confirm, or prompt. A DOM modal requires normal element locators.

Should I wait for the modal or its button?

Wait for the dialog to be visible, then locate and interact with the button inside it. Add a result wait after the click.

Why does waiting for staleness never finish?

Many modal implementations hide a reusable node instead of removing it. Wait for it to become not displayed or assert the success state.

Is clicking the backdrop a good way to close the modal?

Only test backdrop dismissal when it is an intended product behavior. Otherwise click the explicit close or cancel control.

Which AngularJS version does this guidance cover?

The principles apply broadly. The cited UI Bootstrap reference is version 2.3.2, and the Bootstrap event reference is version 4.6; verify your application’s versions and markup.