0

I’m working with Playwright (Node.js) and need to capture notification toasts that appear on the page.
The issue is that the toasts don’t always have the same structure:

  • Some have only a message

  • Some have a title and a message

  • They also change their class (toast-info, toast-warning, toast-error, etc.)

When I try to capture both the title and the message, Playwright sometimes freezes, waiting for .toast-title when it doesn’t exist. I need a safe and generic way to capture all possible cases without running into timeouts.

<!-- example of toast with only message -->
<div id="toast-container" class="toast-top-right" aria-live="polite" role="alert">
  <div class="toast toast-info" style="display: block;">
    <button class="toast-close-button" role="button"><i class="ion-android-close"></i></button>
    <div class="toast-message">Nota Lesión Activa: Lesiones 364</div>
  </div>
</div>

<!-- example of toast with title and message -->
<div id="toast-container" class="toast-top-right" aria-live="polite" role="alert">
  <div class="toast toast-error" style="display: block;">
    <button class="toast-close-button" role="button"><i class="ion-android-close"></i></button>
    <div class="toast-title">Error</div>
    <div class="toast-message">Paciente no encontrado</div>
  </div>
</div>

Error observed

Sometimes my test fails with a timeout when trying to get the toast title:

Get text content(
page.locator('#toast-container .toast').first().locator('.toast-title')
)
— 30.0s
waiting for locator('#toast-container .toast').first().locator('.toast-title')
Timeout 30000ms exceeded.

What I tried

I created two functions: one (boton) that clicks a button and checks for toasts, and another (Capturaerror) that extracts the toast-title and toast-message.

async function boton(page, Accion, indice = 0) {
    await page.getByRole('button', { name: Accion }).nth(indice).click({ timeout: 1000 });
    const toasts = page.locator('#toast-container .toast, #toast-container .toast-error, #toast-container .toast-warning');
    const count = await toasts.count();
    const mensajes = [];

    for (let i = 0; i < count; i++) {
        const toast = toasts.nth(i);
        const clase = await toast.getAttribute('class') || '';
        const mensaje = await toast.locator('.toast-message').textContent().catch(() => '');

        if (clase.includes('toast-error')) {
            if (mensaje && mensaje.trim() !== '') {
                mensajes.push(`${i + 1}.- ${mensaje.trim()}`);
            }
        } else if (clase.includes('toast-warning')) {
            const msj = await Capturaerror(page);
            if (msj && msj.length > 0) {
                throw new Error(`${msj.join('\n')}`);
            }
        } 
    }

    if (mensajes.length > 0) {
        throw new Error(mensajes.join("\n"));
    }
}

async function Capturaerror(page) {
    const toasts = await page.locator('#toast-container .toast').all();
    const mensajes = [];

    let index = 1;
    for (const toast of toasts) {
        const titulo = await toast.locator('.toast-title').textContent().catch(() => '');
        const mensaje = await toast.locator('.toast-message').textContent().catch(() => '');
        if (titulo || mensaje) {
            mensajes.push(`${index}.- ${titulo.trim()}: ${mensaje.trim()}`);
            index++;
        }            
    }
    return mensajes;
}

What I expected

To always capture the visible text of each toast, regardless of whether it has a title, a message, or both.
The .catch(() => '') should skip missing .toast-title without freezing.


What actually happens

If a toast does not contain a .toast-title, Playwright waits for it until the timeout (30s), which causes the test to freeze.


Notes

  • I want a single generic solution that safely captures the title and/or message, or the whole toast text, without timeouts.

  • Node.js / Playwright environment.

1
  • You've defined two functions, but never called them. Please share the site under test (JS/CSS matters here, not just HTML) and complete, runnable code as a minimal reproducible example. Thanks. Commented Sep 30 at 15:48

1 Answer 1

0

Solution you can try:
You need to check if the element exists first, and only then call textContent(). Use element.count() or element.isVisible() or element.isHidden()

try this code

async function CapturaToasts(page) {
    const toasts = await page.locator('#toast-container .toast').all();
    const mensajes = [];

    let index = 1;
    for (const toast of toasts) {
        let titulo = '';
        let mensaje = '';

        const titleLocator = toast.locator('.toast-title');
        const messageLocator = toast.locator('.toast-message');

        if (await titleLocator.count() > 0) {
            titulo = (await titleLocator.textContent())?.trim() || '';
        }

        if (await messageLocator.count() > 0) {
            mensaje = (await messageLocator.textContent())?.trim() || '';
        }

        if (titulo || mensaje) {
            if (titulo && mensaje) {
                mensajes.push(`${index}.- ${titulo}: ${mensaje}`);
            } else {
                mensajes.push(`${index}.- ${titulo || mensaje}`);
            }
            index++;
        }
    }

    return mensajes;
}
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.