Protractor: Evite esperar por $ timeout () s?

Criado em 16 out. 2013  ·  59Comentários  ·  Fonte: angular/protractor

Em nosso aplicativo, exibimos mensagens "pop-up" para nosso usuário quando certos botões são clicados. Essas mensagens pop-up são visíveis apenas por cerca de 20 segundos (após os quais $ timeout as remove).

O problema é que o Protractor parece esperar até que o $ timeout seja concluído antes de fazer as coisas .findElement () e .expect (). No entanto, quando o $ timeout for _timed out_, a mensagem se foi e o elemento não foi encontrado.

Alguma sugestão sobre como contornar / resolver isso? (Nós _precisamos_ afirmar que a mensagem "popup" correta é exibida para o usuário)

obrigado

question

Comentários muito úteis

A equipe Angular está planejando reabrir este problema porque mais e mais pessoas se deparam com isso. Não é profissional apenas fingir que não há problema porque você pode substituir $ timeout por $ interval porque o código não deve ser escrito para se ajustar aos testes - os testes devem ser escritos para testar o código.

Todos 59 comentários

Você pode usar ptor.ignoreSynchronization , mas isso pode causar descamação em outras partes do seu teste. Alternativamente, no Angular 1.2rc3, agora há um serviço interval , que o Protractor não espera (consulte https://github.com/angular/angular.js/commit/2b5ce84fca7b41fca24707e163ec6af84bc12e83). Você pode definir um intervalo que se repetirá 1 vez.

Olá @drhumlen , tivemos o mesmo problema e concluímos que o pop-up é definitivamente algo que deve ir para o teste de unidade. Então, nós simulamos nosso sistema de notificação e esperamos que o mock tenha sido chamado (eventualmente com algumas informações críticas). Também testamos a unidade de notificação para ter certeza de que nada quebra.
Espero que ajude.

$interval fez o truque para nós.

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

Agora funciona muito bem. Mas temos que ser muito cuidadosos e nos certificar de que o pop-up seja removido de alguma outra forma após cada teste (no nosso caso, clicando no botão "x"):

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

@mackwic : Suponho que seja possível testá-lo com um teste de unidade para muitos aplicativos, mas no nosso caso, as mensagens de erro são geradas pelo backend, então temos que testá-las no e2e.

Obrigado @juliemr

Parece estranho que o Transferidor espere $timeout s. Na maioria das vezes, o usuário teria que trabalhar contra isso?

Também enfrentei esse problema e o resolvi com $interval . Com um pequeno toque, você pode usá-lo como $timeout .

Eu queria testar o método de salvamento e verificar se a notificação de sucesso é exibida após salvar. Esta notificação estava sendo ocultada pelo serviço $ timeout. O transferidor estava esperando $ timeout e verificando se a notificação estava visível depois de ter sido ocultada.

Antes:

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

Depois de:

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

Isso não é considerado um problema válido? Eu só corri para isso sozinho. Eu realmente não vejo que usar $interval vez de $timeout seja uma solução, mais uma solução alternativa.

Eu também não consigo fazer isso funcionar, mesmo se eu mudar para $interval de $timeout .

Eu tenho um brinde que desaparece após um intervalo. Este é o código que estou usando para testá-lo:

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

A marcação relevante está aqui (jade)

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

A falha é que toastMessage = $('toast-message') não corresponde a nenhum elemento. Isso está falhando porque está sendo chamado no início, quando .toast-message não existe?

Este problema já foi corrigido? Porque usar intervalo em vez de tempo limite é hack ou workarounf mais do que solução.

Eu enfrento o mesmo problema e browser.ignoreSynchronization = true; não ajuda. Posso substituir o código por $ interval, mas os testes e2e devem testar o código real, não o código adotado por eles.

@ vytautas-pranskunas- parece que há um problema diferente para você se ignoreSynchronization não estiver funcionando. Se você puder fornecer um exemplo reproduzível, abra um novo problema.

Eu descobri quando ignoreSynchronization não está funcionando:
Tenho um serviço responsável por mostrar mensagens de erro. Depois de mostrar a mensagem, ela permanece na tela por cinco segundos após desaparecer. Este processo é controlado por $ timeout.

Eu tenho dois cenários - no primeiro ignoreSynchronization funciona bem, no segundo - não

1) Eu sei que as mensagens devem aparecer imediatamente após o usuário clicar no botão e eu faço expec(message).tobe(...) neste caso, ignoreSynchronization faz o trabalho.

2) Eu sei que a mensagem pode aparecer após qualquer período de tempo (depende do tempo de resposta do servidor) e minha lógica adicional deve ser executada somente depois de aparecer, então, neste caso, eu tenho que usar browser.wait(by.(....).isPresent(), n) aqui ignoreSynchronization para de funcionar e o fluxo segue (mostrarei a saída de browser.wait console.log enquanto aguardo)

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

Como você pode ver, ele nunca vê o elemento presente porque a espera congela ao entrar em contato com $ timeout

Eu realmente não tenho muito tempo para torná-lo reproduzível no plunker, mas se você tentar seria ótimo porque este cenário deve ser bastante comum.

Thnaks

Você loga realmente não é muito útil sem o contexto do código que o criou. Você poderia compartilhar isso?

Não posso compartilhar meu código por causa da privacidade, mas posso mostrar alguns pseudocódigos para ter uma ideia
(não escrever código html porque há um botão e ng-repeat para exibir mensagens)

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;
    }

e parte do transferidor

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 encontra esse elemento porque, enquanto o elemento estiver presente, browse.wait não está acionando verificações.

Existe algum progresso ou pensamento sobre isso?

Por que "browser.ignoreSynchronization = false;" não pode ser definido imediatamente após o browser.wait ()?

Meu 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

Sendo mordido pelo mesmo problema. O módulo toast que usamos é um componente genérico para npm, portanto, não podemos alterá-lo facilmente para usar $ interval. Também não consigo ver por que o transferidor deve esperar por um e não pelo outro. Idealmente, isso seria configurável por conta própria, portanto, não afeta outras esperas de sincronização.

Temos esse problema em angular-ui / bootstrap # 3982 e decidimos não trocá-lo por $ interval, então viemos aqui para ver se há algo que pode ser feito para corrigir o problema.

Muito obrigado.

Corri este problema esta semana usando um componente de notificação do sistema.

Usar $interval vez de $timeout não é uma solução plausível. E browser.ignoreSynchronization = true também não funciona para mim.

Algum pensamento ou ideia?

A equipe Angular está planejando reabrir este problema porque mais e mais pessoas se deparam com isso. Não é profissional apenas fingir que não há problema porque você pode substituir $ timeout por $ interval porque o código não deve ser escrito para se ajustar aos testes - os testes devem ser escritos para testar o código.

1 Precisamos de uma correção também.

Acordado. O teste deve ser corrigido. +1

1, também correu para este problema. Talvez devesse haver uma maneira de dizer que os tempos limite do transferidor não devem ser esperados.

ignoreSynchronisation não funciona para mim, acho que ignora muitas coisas. Precisamos ignorar a sincronização de tempos limite ou ter uma API mais sofisticada como browser.waitForTimeouts(); .

+1. precisa de uma correção. por que devemos usar soluções alternativas feias com intervalo.

+1

1, achando muito difícil lidar com testes instáveis ​​.. preciso de uma correção para isso

+1 concordo com @ vytautas-pranskunas- não devemos ser obrigados a alterar o código que queremos testar apenas para fazer o teste funcionar ....

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

Acho que muitas pessoas podem estar usando browser.ignoreSynchronization = true maneira errada. Por favor, corrija-me se eu estiver errado, mas para mim se você escrever:

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

Então não vai funcionar: isso ainda é JavaScript puro e as três linhas serão lidas de forma síncrona. Portanto, quando as promessas getText e expect forem resolvidas, a sincronização já estará ativada novamente. Você pode contornar isso esperando que o transferidor resolva a última promessa e volte a sincronização em seu retorno de chamada then .

Uma maneira da equipe do transferidor ajudar as pessoas pode ser fornecer acesso a métodos que afetam a sincronização apenas quando o buffer de promessas é concluído. Por exemplo:

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

De qualquer forma, também tive muitos problemas devido ao Protractor esperar por $ timeouts: em aplicativos grandes, há muitos efeitos colaterais, sejam algumas dicas de ferramentas, modais, animações, etc. Decidimos desabilitar globalmente a sincronização com browser.ignoreSynchronization = true; e escreva testes da mesma forma que um usuário testaria o produto: espere um elemento ficar visível se quiser clicar nele, etc. Escreva alguns métodos auxiliares e será muito fácil. Isso funciona muito bem para nós e torna os testes muito mais rápidos.

+1 usando $ intervalo é um hack, não uma solução

+1 este bug me causou muito sofrimento. Vamos iniciar uma caixa de diálogo sobre como consertar isso. Eu tenho várias dezenas de módulos angular bower e npm que usam $ timeout, em centenas (!) De lugares, e também o uso 45 vezes em meu código. Alterar meu código é viável (e razoável), no entanto, alterar o código de terceiros está totalmente fora de questão logisticamente: entrar em contato e tentar motivar os fornecedores upstream ou bifurcar localmente módulos angulares de terceiros e manter um fluxo de patch paralelo é um cenário de pesadelo que não é aceitável, quando o bug está com o Protractor e $ timeout, não fora dos módulos.

Por favor, vamos trabalhar juntos nisso.

+1

Este bug está criando problemas para o teste e2e para o cenário de amostra abaixo,

1) Preencha os campos e envie algum formulário, que irá transmitir vários eventos como
* link da mensagem de status, anexado com $ timeout e visível por apenas alguns segundos
* uma nova mensagem é adicionada à lista de mensagens em um widget diferente
2) Em seguida, ao clicar no link da mensagem de status, ele deve abrir a mensagem como pop-up com os detalhes

Portanto, quando estamos tentando automatizar o cenário, podemos enviar o formulário. Mas quando estamos tentando clicar no link da mensagem de status, precisamos ter browser.ignoreSynchronization = true; porque o link está anexado a $ timeout.

O link é clicável, mas uma mensagem aberta como um pop-up não está funcionando.

Quando chequei no Stackoverflow e em algumas respostas do google, descobri que era por causa do $ timeout. Então, eu verifiquei removendo $ timeout para o elemento, agora ele funciona corretamente.

Portanto, minha solicitação é toda vez que não podemos ter um elemento sem $ timeout. Portanto, considere o cenário acima e tenha um novo recurso em que o transferidor pode ignorar a espera por $ timeout.

Enfrentando o mesmo problema, tive que usar window.setTimeout e window.clearTimeout para evitá-lo. Não é muito angular e pode interferir na digestão, mas $ intervalo e ignorar a sincronização não é uma solução

Enfrentando exatamente o mesmo problema, alguém da equipe do transferidor responderá a isso?

+1

Isso estava me deixando louco (o transferidor noob aqui) até que cheguei a uma solução suja como abaixo:

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

Funciona bem e não é muito lento em um navegador sem cabeça.

+1

Também encontrando um problema aqui. Meus testes estavam indo bem até visitar uma página que faz algumas solicitações de API e abre alguns WebSockets. Nesse ponto, o tempo limite do Transferidor expira, todas as vezes. Não descobri uma boa solução.

Estou curioso para @floribon sobre seus métodos auxiliares sugeridos se você desativar browser.ignoreSynchronization = true; globalmente. No momento, estou usando objetos de página, que tendem a se parecer com:

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, meus ajudantes são assim:

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();

Com, por exemplo, wait.forElement devolvendo

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');

A ideia é imitar o comportamento real do usuário: eu clico em um elemento, espero até que outro esteja visível, depois clico nele etc. Dessa forma, não é necessário esperar $ timeout, é mais rápido e seguro.

A chave aqui é ignorar os erros da promessa interna e fazer mais algumas tentativas até que o tempo limite seja atingido.

PS: Você também pode usar ExpectedConditions para isso, mas tive alguns problemas com eles quando o Angular reconstruiu o nó DOM (por exemplo ng-if conditions):

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

@floribon incrível, obrigado!

@juliemr pode me ajudar, por favor, a capturar as mensagens de erro do sistema no transferidor.Pls responda no meu e-mail id anjali. [email protected]

@juliemr O uso do serviço $ interval para invocar solicitações http de longa execução não está funcionando. O transferidor ainda está tendo tempo limite.
Aqui está meu código angular
este. $ intervalo (() => este. $ http.get(this.prefix (url), config), 0, 1)
transferidor ainda esperando a conclusão da tarefa $ http.
Estou usando o transferidor 5.xe angualrjs 1.6x.
Você poderia me ajudar?

+1, algo deve ser adicionado para lidar com isso.

+1

+1

+1

+1

+1

+1

Esta especificação funcionou para mim. Muito obrigado! e saudações de @floribon pela dica de que a execução é assíncrona.

    // 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;
                })
            })
        });
    }

pessoal, como posso adicionar tempo limite para um determinado caso de teste
`
it ('1-deve fazer o login com um código de registro enviado para um e-mail', função (feito) {

// 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();
                });


    });`
Esta página foi útil?
0 / 5 - 0 avaliações