Protractor: Evite esperar $ timeout () s?

Creado en 16 oct. 2013  ·  59Comentarios  ·  Fuente: angular/protractor

En nuestra aplicación, mostramos mensajes "emergentes" a nuestro usuario cuando se hace clic en ciertos botones. Estos mensajes emergentes solo son visibles durante unos 20 segundos (después de los cuales $ timeout los elimina).

El problema es que Protractor parece esperar hasta que se complete $ timeout antes de que las cosas .findElement () y .expect () s. Sin embargo, cuando el $ timeout es _timed out_, el mensaje desaparece y no se encuentra el elemento.

¿Alguna sugerencia sobre cómo omitir / resolver esto? (Necesitamos_ aseverar que se muestra al usuario el mensaje "emergente" correcto)

Gracias

question

Comentario más útil

¿El equipo de Angular está planeando reabrir este problema porque cada vez más personas se encuentran con esto? No es profesional simplemente pretender que no hay ningún problema porque puede reemplazar $ timeout con $ interval porque el código no debe escribirse para ajustarse a las pruebas; las pruebas deben escribirse para probar el código.

Todos 59 comentarios

Puede usar ptor.ignoreSynchronization , pero esto puede causar escamas en otras partes de su prueba. Alternativamente, en Angular 1.2rc3, ahora hay un servicio interval , que Transportador no esperará (consulte https://github.com/angular/angular.js/commit/2b5ce84fca7b41fca24707e163ec6af84bc12e83). Puede establecer un intervalo que se repetirá 1 vez.

Hola @drhumlen , tuvimos el mismo problema y llegamos a la conclusión de que la ventana emergente es definitivamente algo que debería someterse a pruebas unitarias. Entonces, nos burlamos de nuestro sistema de notificación y esperamos que se haya llamado al simulacro (eventualmente con algunas informaciones críticas). También probamos la notificación por unidad para asegurarnos de que nada se rompa.
Espero que te ayude.

$interval sirvió.

  $interval(function() {
    // ...
  }, duration, 1);

Ahora funciona muy bien. Pero tenemos que ser muy concienzudos y asegurarnos de que la ventana emergente se elimine de alguna otra manera después de cada prueba (en nuestro caso, haciendo clic en el botón "x"):

  afterEach(function() {
    removeLogMessagesFromDOM();
  });

@mackwic : Supongo que es posible probarlo con una prueba unitaria para muchas aplicaciones, pero en nuestro caso, los mensajes de error los genera el backend, por lo que tenemos que probarlos en el e2e.

Gracias @juliemr

Parece extraño que Transportador espere $timeout s. ¿La mayoría de las veces el usuario tendría que trabajar en contra de esto?

También enfrentó este problema y lo resolvió con $interval . Con un pequeño giro, puedes usarlo como $timeout .

Quería probar el método de guardado y verificar que se muestre la notificación de éxito después de guardar. El servicio $ timeout estaba ocultando esta notificación. Transportador estaba esperando $ timeout y verificando si la notificación es visible después de que ya se había ocultado.

Antes de:

$timeout(function() {
    //hide notification
}, 3000);

Después:

var intervalCanceller = $interval(function() {
    //hide notification
    $interval.cancel(intervalCanceller);
}, 3000);

¿No se considera un problema válido? Me encontré con esto yo mismo. Realmente no veo que usar $interval lugar de $timeout sea ​​una solución, más una solución.

Tampoco puedo hacer que esto funcione, incluso si cambio a $interval de $timeout .

Tengo un brindis que desaparece después de un tiempo de espera. Este es el código que estoy usando para probarlo:

username = element(select.model("user.username"))
password = element(select.model("user.password"))
loginButton = $('button[type=\'submit\']')
toast = $('.toaster')
# Check the toaster isn't displayed initially
expect(toast.isDisplayed()).toBe(false)  # This passes
username.sendKeys("redders")
password.sendKeys("wrongpassword")
loginButton.click()
toastMessage = $('toast-message')
# Check the toaster is now displayed
expect(toast.isDisplayed()).toBe(true)   # This passes
expect(toastMessage.getText()).toBe("Invalid password")  # This fails

El marcado relevante está aquí (jade)

.toaster(ng-show="messages.length")
  .toast-message(ng-repeat="message in messages") {{message.body}}

El error es que toastMessage = $('toast-message') no coincide con ningún elemento. ¿Esto está fallando porque se está llamando al principio, cuando .toast-message no existe?

¿Este problema ya se solucionó? Porque usar el intervalo en lugar del tiempo de espera es un truco o una solución alternativa más que una solución.

Me enfrento al mismo problema y browser.ignoreSynchronization = true; no ayuda. Puedo reemplazar el código por $ interval, pero las pruebas e2e deberían probar el código real, no el código adoptado.

@ vytautas-pranskunas- parece que hay un problema diferente para ti si ignoreSynchronization no funciona. Si puede proporcionar un ejemplo reproducible, abra un nuevo problema.

Me di cuenta de que ignoreSynchronization no funciona:
Tengo un servicio que se encarga de mostrar mensajes de error. El mensaje Una vez que se muestra permanece en la pantalla durante cinco segundos después de que desaparece. Este proceso está controlado por $ timeout.

Tengo dos escenarios: en el primer ignoreSynchronization funciona bien, en el segundo, no

1) Sé que los mensajes deben aparecer inmediatamente después de que el usuario haga clic en el botón y yo hago expec(message).tobe(...) en este caso ignoreSynchronization hace el trabajo.

2) Sé que el mensaje puede aparecer después de cualquier período de tiempo (depende del tiempo de respuesta del servidor) y mi lógica adicional debe ejecutarse solo después de que aparezca, por lo que en este caso tengo que usar browser.wait(by.(....).isPresent(), n) aquí ignoreSynchronization deja de funcionar y el flujo sigue (mostraré el resultado de browser.wait console.log mientras espero)

waits
waits
waits (element appears)
browser waits freezes for 5 seconds
after element disappears it continue
wait
wait
wait

Entonces, como puede ver, nunca ve el elemento presente porque la espera se congela cuando está en contacto con $ timeout

Realmente no tengo tanto tiempo para hacerlo reproducible en plunker, pero si lo intentas, sería genial porque este escenario debería ser bastante común.

Gracias

Realmente, iniciar sesión no es muy útil sin el contexto del código que lo creó. ¿Podrías compartir eso?

No puedo compartir mi código debido a la privacidad, pero puedo mostrar un pseudocódigo para tener una idea
(no escribir código html porque hay un botón y ng-repeat para mostrar mensajes)

constructor(){
$scope.$on('globalMessageAdded', () => {
            $scope.globalMessages = this.getMessages();
        });
}

callServer(){
//interval here acts like server delay
   $interval(()=> {
     $rootScope.messages.push('test messages');
      this.$rootScope.$broadcast('globalMessageAdded');
   }, 3000, 1);
}

getMessages = () => {
        var newMessages = <ISpaGlobalMessage[]>[];
        _.forEach(this.$rootScope.globalMessages, item => {
            newMessages.push(item);
//because of this timeout browser.wait stops waitng for 5 seconds
            this.$timeout(() => {
                this.remove(item);
                this.$rootScope.$broadcast('globalMessageRemoved');
            }, 5000);
        });

        return newMessages;
    }

y parte del transportador

element('button').click();
browser.ignoreSynchronization = true; //tried with and without it
browser.wait(() => element(by.css('.alert-success')).isPresent(), 10000000);
browser.ignoreSynchronization = false;

other code that should be executed after successful message

browser.wait nunca encuentra ese elemento porque mientras el elemento está presente, browse.wait no activa comprobaciones.

¿Hay algún progreso o pensamientos sobre esto?

¿Por qué "browser.ignoreSynchronization = false;" no se puede establecer inmediatamente después de browser.wait ()?

Mi código

        var el = element(by.css('.popup-title.ng-binding'));
        browser.ignoreSynchronization = true; 
        browser.wait(function (){
            return el.isPresent();
        }, 10000);
        var popupMsg = el.getText();
        expect(popupMsg).toEqual('something...');
        browser.ignoreSynchronization = false; // not work, if take out this statement, it works again

Ser mordido por el mismo problema. El módulo toast que usamos es un componente genérico para npm, por lo que no podemos cambiarlo fácilmente para usar $ interval. Tampoco veo por qué el transportador debería esperar a uno y no al otro. Idealmente, esto sería configurable por sí solo, por lo que no afecta otras esperas de sincronización.

Tenemos este problema en angular-ui / bootstrap # 3982 y decidimos no cambiarlo a $ interval, así que venimos aquí para ver si hay algo que se pueda hacer para solucionar el problema.

Muchas gracias.

Me encontré con este problema esta semana usando un componente de notificación de tostadas.

Usar $interval lugar de $timeout no es una solución plausible. Y browser.ignoreSynchronization = true tampoco me funciona.

¿Alguna idea o pensamiento?

¿El equipo de Angular está planeando reabrir este problema porque cada vez más personas se encuentran con esto? No es profesional simplemente pretender que no hay ningún problema porque puede reemplazar $ timeout con $ interval porque el código no debe escribirse para ajustarse a las pruebas; las pruebas deben escribirse para probar el código.

+1 También necesitamos una solución.

Convenido. La prueba debe arreglarse. +1

+1, también encontré este problema. Tal vez debería haber una manera de indicar que los tiempos de espera del transportador no deben esperarse.

ignoreSynchronisation no funciona para mí, creo que ignora demasiadas cosas. Necesitamos una forma de ignorar la sincronización de tiempos de espera o tener una API más sofisticada como browser.waitForTimeouts(); .

+1. necesita una solución. ¿por qué deberíamos usar soluciones desagradables con intervalo?

+1

+1, resulta muy difícil manejar pruebas inestables. Necesito una solución para esto

+1 de acuerdo con @ vytautas-pranskunas- no deberíamos tener que cambiar el código que queremos probar solo para que la prueba funcione ...

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

Creo que muchas personas pueden estar usando browser.ignoreSynchronization = true de manera incorrecta. Por favor corríjame si me equivoco, pero a mí si escribe:

browser.ignoreSynchronization = true;
expect( elem.getText() ).toEqual( "foo" );
browser.ignoreSynchronization = false;

Entonces no funcionará: esto sigue siendo JavaScript puro y las tres líneas se leerán sincrónicamente. Entonces, para cuando se resuelvan las promesas de getText y expect , la sincronización ya estará activada. Puede solucionarlo esperando que el transportador resuelva la última promesa y vuelva a activar la sincronización en su devolución then llamada

Una forma en que el equipo del transportador puede ayudar a las personas puede ser brindar acceso a métodos que solo afectan la sincronización cuando se completa el búfer de promesas. Por ejemplo:

browser.setIgnoreSynchronization( true );
expect( elem.getText() ).toEqual( "foo" );
browser.setIgnoreSynchronization( false );

De todos modos, también me he encontrado con muchos problemas debido a que Transportador espera $ timeouts: en aplicaciones grandes hay demasiados efectos secundarios, ya sean información sobre herramientas, modales, animaciones, etc. Decidimos deshabilitar globalmente la sincronización con browser.ignoreSynchronization = true; y escriba pruebas de la misma manera que un usuario probaría el producto: espere a que un elemento sea visible si desea hacer clic en él, etc. Escriba usted mismo algunos métodos auxiliares y será muy fácil. Esto funciona bastante bien para nosotros y hace que las pruebas se ejecuten mucho más rápido.

+1 usando $ interval es un truco, no una solución

+1 este error me ha causado tanta miseria. Iniciemos un diálogo sobre cómo solucionar este problema. Tengo varias docenas de módulos angulares bower y npm que usan $ timeout, en cientos (!) De lugares, y también lo uso 45 veces en mi código. Cambiar mi código es factible (y razonable), sin embargo, cambiar el código de terceros está totalmente fuera de discusión desde el punto de vista logístico: contactar e intentar motivar a los proveedores ascendentes o bifurcar localmente módulos angulares de terceros localmente y mantener un flujo de parches paralelo es un escenario de pesadilla que no es aceptable, cuando el error es con Transportador y $ timeout, no con módulos externos.

Trabajemos juntos en esto.

+1

Este error está creando problemas para tener la prueba e2e para el siguiente escenario de muestra,

1) Complete los campos y envíe algún formulario, que transmitirá múltiples eventos como
* enlace de mensaje de estado, que se adjunta con $ timeout y es visible solo durante unos segundos
* se agrega un nuevo mensaje a la lista de mensajes en un widget diferente
2) A continuación, cuando haga clic en el enlace del mensaje de estado, debería abrir el mensaje como una ventana emergente con detalles

Entonces, cuando intentamos automatizar el escenario, podemos enviar el formulario. Pero cuando intentamos hacer clic en el enlace del mensaje de estado, necesitamos tener browser.ignoreSynchronization = true; porque el enlace está adjunto a $ timeout.

Se puede hacer clic en el enlace, pero un mensaje que se abre como una ventana emergente no funciona.

Cuando revisé Stackoverflow y algunas respuestas de Google, supe que se debe al $ timeout. Así que lo comprobé eliminando $ timeout para el elemento, ahora funciona correctamente.

Entonces, mi solicitud es cada vez que no podemos tener un elemento sin $ timeout. Por lo tanto, considere el escenario anterior y tenga una nueva función en la que el transportador puede ignorar la espera de $ timeout.

Enfrenté el mismo problema, tuve que usar window.setTimeout y window.clearTimeout para evitarlo. No es una forma muy angular y puede interferir con el resumen, pero $ interval e ignorar la sincronización no es una solución

Enfrentado exactamente el mismo problema, ¿alguien del equipo de transportadores responderá a esto?

+1

Esto me estaba volviendo loco (novato transportador aquí) hasta que llegué a una solución sucia como se muestra a continuación:

beforeEach(function () { browser.ignoreSynchronization = true; });
afterEach(function () { browser.restart(); });

Funciona bien y no es demasiado lento en un navegador sin cabeza.

+1

También encontré un problema aquí. Mis pruebas se realizaron sin problemas hasta que visité una página que realiza algunas solicitudes de API y abre algunos WebSockets. En ese momento, el transportador expira cada vez. No he encontrado una buena solución.

Tengo curiosidad, @floribon, acerca de sus métodos de ayuda sugeridos si desactiva browser.ignoreSynchronization = true; globalmente. En este momento estoy usando objetos de página, que tienden a verse así:

describe('something', function() {
  it('should do....', function() {
    var fooPage = new FooPage();
    fooPage.visit();
    var barPage = fooPage.clickCreate();  // does some API call, then redirects to barPage
    // at this point, enough things are happening that it is impossible to interact with barPage.
    // Protractor locks up.
    // barPage makes other API call & opens WebSocket
    barPage.clickBaz();  // nope.  will timeout every time.
  });
});

@benjaminapetersen mis ayudantes se ven así:

btn1.click();         // Triggers many things and eventually displays page2
wait.forURL('page2'); // wait for page2 to be loaded before moving on
$('.btn1').click();   // this does some async job and eventually displays btn2
wait.forElement('.btn2');
$('.btn2').click();

Con, por ejemplo, wait.forElement regresando

browser.wait(() => elem.isPresent().then(p => p, () => {}), timeout, 'Not found');
// browser.wait(function() { return elem.isPresent().then(function(p) { return p;} , function() {}); }, timeout, 'Not found');

La idea es imitar el comportamiento real del usuario: hago clic en un elemento, espero hasta que otro sea visible, luego hago clic en él, etc. De esta manera no es necesario esperar $ timeout, es más rápido y seguro.

La clave aquí es ignorar los errores de la promesa interna y darle algunos intentos más hasta que se agote el tiempo de espera.

PD: también puede usar ExpectedConditions para eso, pero tuve algunos problemas con ellos cuando angular reconstruía el nodo DOM (por ejemplo, ng-if condiciones):

browser.wait(browser.ExpectedConditions.precenseOf(elem), timeout, 'Not found');

@floribon increíble, ¡gracias!

@juliemr ¿ puedes ayudarme, por favor, a captar los mensajes de error de tostadas en el transportador? Por favor, responde en mi identificación de correo anjali. [email protected]

@juliemr El uso del servicio $ interval para invocar solicitudes http de ejecución prolongada no funciona. El transportador todavía tiene tiempo de espera.
Aquí está mi código angular
esto. $ intervalo (() => esto. $ http.get(este.prefijo (url), config), 0, 1)
transportador sigue esperando que se complete la tarea $ http.
Estoy usando el transportador 5.xy angualrjs 1.6x.
¿Usted me podría ayudar?

+1, se debe agregar algo para manejar esto.

+1

+1

+1

+1

+1

+1

Esta especificación funcionó para mí. ¡Muchas gracias! y saludos a @floribon por la pista de que la ejecución es asincrónica.

    // screenshot-spec.js

    // a close button that appears on the md-toast template
    const closeToastButton = $('[data-automation="toast-close"]')
    const cond = protractor.ExpectedConditions
    function waitToastShow() {
        return browser.wait(cond.elementToBeClickable(closeToastButton), 5000)
    }
    function waitToastHide() {
        return browser.wait(cond.invisibilityOf(closeToastButton), 5000)
    }

    screenshot = name => browser.takeScreenshot().then(/* save fn */)

    describe('a suite ... ', () => {
        it('takes screenshots of an md-toast once shown and after hidden', function () {
            // ... actions that launch an md-toast using $mdToast.show({ ... })
            browser.ignoreSynchronization = true
            waitToastShow().then(() => {
                screenshot('toast-showing.png')
                waitToastHide().then(() => {
                    screenshot('toast-hidden.png')
                    browser.ignoreSynchronization = false;
                })
            })
        });
    }

chicos, ¿cómo puedo agregar tiempo de espera para un caso de prueba determinado?
'
it ('1-debería iniciar sesión con un código de registro enviado a un correo electrónico', función (hecho) {

// setTimeout(function () {
            flow.execute(browser.params.getLastEmail)
                .then(function (email) {
                    expect(email.subject)
                        .toEqual('[email protected] submitted feedback');
                    expect(email.headers.to)
                        .toEqual('[email protected]');
                    expect(email.html.includes('User feedback details: accountId: 12345, related To: dashboard, description: ' + D.feedbackMsg + ''))
                        .toEqual(true);
                    console.log(email.html);
                    // done();
                });


    });`
¿Fue útil esta página
0 / 5 - 0 calificaciones