Protractor: Évitez d'attendre les $ timeout () s?

Créé le 16 oct. 2013  ·  59Commentaires  ·  Source: angular/protractor

Dans notre application, nous affichons des messages "popup" à notre utilisateur lorsque certains boutons sont cliqués. Ces messages contextuels ne sont visibles que pendant environ 20 secondes (après quoi $ timeout les supprime).

Le problème est que Protractor semble attendre la fin du $ timeout avant de faire les choses .findElement () et .expect (). Cependant, lorsque le $ timeout est _timed out_, le message est parti et l'élément est introuvable.

Une suggestion sur la façon de contourner / résoudre ce problème? (Nous _ devons_ affirmer que le message "popup" correct est affiché à l'utilisateur)

Merci

question

Commentaire le plus utile

L'équipe Angular envisage-t-elle de rouvrir ce problème parce que de plus en plus de gens le rencontrent? Ce n'est pas professionnel de prétendre simplement qu'il n'y a pas de problème car vous pouvez remplacer $ timeout par $ intervalle car le code ne doit pas être écrit pour s'adapter aux tests - les tests doivent être écrits pour tester le code.

Tous les 59 commentaires

Vous pouvez utiliser ptor.ignoreSynchronization , mais cela peut provoquer des flocons ailleurs dans votre test. Alternativement, sur Angular 1.2rc3, il existe maintenant un service interval , que Protractor n'attendra pas (voir https://github.com/angular/angular.js/commit/2b5ce84fca7b41fca24707e163ec6af84bc12e83). Vous pouvez définir un intervalle qui se répétera 1 fois.

Salut @drhumlen , nous avons eu le même problème et nous concluons que le popup est certainement quelque chose qui devrait aller au test unitaire. Donc, nous nous sommes moqués de notre système de notification et nous nous attendons à ce que la maquette ait été appelée (éventuellement avec des informations critiques). Nous avons également testé la notification unitaire pour nous assurer que rien ne casse.
J'espère que cela vous aidera.

$interval fait l'affaire pour nous.

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

Cela fonctionne maintenant très bien. Mais nous devons être très consciencieux et nous assurer que le popup est supprimé d'une autre manière après chaque test (dans notre cas, en cliquant sur le bouton "x"):

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

@mackwic : Je suppose qu'il est possible de le tester avec un test unitaire pour de nombreuses applications, mais dans notre cas, les messages d'erreur sont générés par le backend, nous devons donc les tester dans l'e2e.

Merci @juliemr

Il semble étrange que Protractor attende $timeout s. La plupart du temps, l'utilisateur devrait travailler contre cela?

Également confronté à ce problème et l'a résolu avec $interval . Avec une petite torsion, vous pouvez l'utiliser comme $timeout .

Je voulais tester la méthode de sauvegarde et vérifier que la notification de réussite s'affiche après la sauvegarde. Cette notification était masquée par le service $ timeout. Le rapporteur attendait $ timeout et vérifiait si la notification est visible après avoir été masquée.

Avant:

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

Après:

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

N'est-ce pas considéré comme un problème valable? Je viens de rencontrer cela moi-même. Je ne vois pas vraiment que l'utilisation de $interval au lieu de $timeout est une solution, plus une solution de contournement.

Je ne peux pas non plus faire fonctionner cela, même si je passe à $interval partir de $timeout .

J'ai un toast qui disparaît après un certain temps. Voici le code que j'utilise pour le tester:

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

Le balisage pertinent est ici (jade)

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

L'échec est que toastMessage = $('toast-message') ne correspond à aucun élément. Est-ce que cela échoue parce qu'il est appelé au début, quand .toast-message n'existe pas?

Ce problème a-t-il encore été résolu? Parce que l'utilisation de l'intervalle au lieu du délai d'expiration est plus qu'une solution.

Je suis confronté au même problème et browser.ignoreSynchronization = true; n'aide pas. Je peux remplacer le code par $ interval mais les tests e2e devraient tester le code réel et non le code qui leur a été adopté.

@ vytautas-pranskunas- semble qu'il y ait un problème différent pour vous si ignoreSynchronization ne fonctionne pas. Si vous pouvez fournir un exemple reproductible, ouvrez un nouveau problème.

J'ai compris quand ignoreSynchronization fonctionnait pas:
J'ai un service qui est chargé d'afficher les messages d'erreur. Une fois le message affiché, il reste à l'écran pendant cinq secondes après qu'il s'estompe. Ce processus est contrôlé par $ timeout.

J'ai deux scénarios - dans le premier ignoreSynchronization fonctionne bien, dans le second - ne fonctionne pas

1) Je sais que les messages devraient apparaître immédiatement après que l'utilisateur clique sur le bouton et je fais expec(message).tobe(...) dans ce cas, ignoreSynchronization fait le travail.

2) Je sais que ce message peut apparaître après n'importe quel laps de temps (dépend du temps de réponse du serveur) et ma logique supplémentaire ne doit être exécutée qu'après son apparition, donc dans ce cas, je dois utiliser browser.wait(by.(....).isPresent(), n) ici ignoreSynchronization cesse de fonctionner et le flux suit (je vais afficher la sortie browser.wait console.log en attendant)

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

Donc, comme vous pouvez le voir, il ne voit jamais d'élément présent car l'attente se fige au contact de $ timeout

Je n'ai vraiment pas beaucoup de temps pour le rendre reproductible dans plunker mais si vous essayez, ce serait génial car ce scénario devrait être assez courant.

Thnaks

Vous vous connectez n'est vraiment pas très utile sans le contexte du code qui l'a créé. Pouvez-vous partager ça?

Je ne peux pas partager mon code pour des raisons de confidentialité, mais je peux afficher un pseudo-code pour avoir une idée
(pas d'écriture de code html car il y a un bouton et ng-repeat pour afficher les messages)

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

et partie rapporteur

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 ne trouve jamais cet élément car tant que l'élément est présent, parcourir.wait ne déclenche pas de vérifications.

Y a-t-il des progrès ou des réflexions à ce sujet?

Pourquoi "browser.ignoreSynchronization = false;" ne peut pas être défini immédiatement après browser.wait ()?

Mon code

        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

Être mordu par le même problème. Le module toast que nous utilisons est un composant générique pour npm, nous ne pouvons donc pas facilement le changer pour utiliser $ intervalle. Je ne vois pas non plus pourquoi le rapporteur devrait attendre l'un et pas l'autre. Idéalement, ce serait configurable seul, donc cela n'affecte pas les autres attentes de synchronisation.

Nous avons ce problème à angular-ui / bootstrap # 3982 et nous avons décidé de ne pas le remplacer par $ intervalle, nous venons donc ici pour voir s'il y a quelque chose qui pourrait être fait pour résoudre le problème.

Merci beaucoup.

J'ai rencontré ce problème cette semaine en utilisant un composant de notification toast.

Utiliser $interval au lieu de $timeout n'est pas une solution plausible. Et browser.ignoreSynchronization = true ne fonctionne pas non plus pour moi.

Des réflexions ou des idées?

L'équipe Angular envisage-t-elle de rouvrir ce problème parce que de plus en plus de gens le rencontrent? Ce n'est pas professionnel de prétendre simplement qu'il n'y a pas de problème car vous pouvez remplacer $ timeout par $ intervalle car le code ne doit pas être écrit pour s'adapter aux tests - les tests doivent être écrits pour tester le code.

+1 Nous avons également besoin d'un correctif.

D'accord. Le test doit être corrigé. +1

+1, a également rencontré ce problème. Peut-être qu'il devrait y avoir un moyen de dire que les délais d'attente du rapporteur ne devraient pas être attendus.

ignorer La synchronisation ne fonctionne pas pour moi, je pense qu'elle ignore trop de choses. Nous avons besoin d'un moyen d'ignorer la synchronisation des délais d'expiration, ou d'avoir une API plus sophistiquée comme browser.waitForTimeouts(); .

+1. besoin d'un correctif. pourquoi devrions-nous utiliser des solutions de contournement laides avec intervalle.

+1

+1, trouvant très difficile de gérer les tests irréguliers .. besoin d'un correctif pour cela

+1 d'accord avec @ vytautas-pranskunas - nous ne devrions pas être obligés de changer le code que nous voulons tester uniquement pour faire fonctionner le test lui-même ....

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

Je pense que beaucoup de gens utilisent peut-être browser.ignoreSynchronization = true de manière incorrecte. Veuillez me corriger si je me trompe, mais à moi si vous écrivez:

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

Ensuite, cela ne fonctionnera pas: il s'agit toujours de JavaScript pur et les trois lignes seront lues de manière synchrone. Ainsi, au moment où les promesses getText et expect résolvent, la synchronisation sera déjà réactivée. Vous pouvez contourner ce problème en attendant que le rapporteur résolve la dernière promesse et rétablisse la synchronisation sur son rappel then .

Une façon pour l'équipe de rapporteur d'aider les gens peut être de fournir un accès à des méthodes qui n'affectent la synchronisation que lorsque le tampon des promesses est terminé. Par exemple:

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

Quoi qu'il en soit, j'ai également rencontré de nombreux problèmes dus au fait que Protractor attend des $ timeouts: dans une grande application, il y a trop d'effets secondaires, que ce soit des info-bulles, des modaux, des animations, etc. Nous avons décidé de désactiver globalement la synchronisation avec browser.ignoreSynchronization = true; et écrire des tests comme un utilisateur testerait le produit: attendez qu'un élément soit visible si vous voulez cliquer dessus, etc. Écrivez-vous quelques méthodes d'aide et cela deviendra un jeu d'enfant. Cela fonctionne plutôt bien pour nous et rend les tests beaucoup plus rapides.

+1 en utilisant $ intervalle est un hack, pas une solution

+1 ce bug m'a causé tellement de misère. Veuillez ouvrir une boîte de dialogue pour savoir comment résoudre ce problème. J'ai plusieurs dizaines de modules angulaires bower et npm qui utilisent $ timeout, à des centaines (!) D'endroits, et je l'utilise 45 fois dans mon code. Changer mon code est faisable (et raisonnable), cependant, changer de code tiers est totalement hors de question sur le plan logistique: contacter et essayer de motiver des fournisseurs en amont ou créer localement des modules angulaires tiers et maintenir un flux de correctifs parallèle est un scénario de cauchemar qui n'est pas acceptable, lorsque le bogue concerne Protractor et $ timeout, pas en dehors des modules.

Veuillez travailler ensemble sur ce point.

+1

Ce bogue crée des problèmes pour tester e2e pour l'exemple de scénario ci-dessous,

1) Remplissez les champs et soumettez un formulaire, qui diffusera plusieurs événements comme
* lien de message d'état, qui est joint avec $ timeout et visible pendant seulement quelques secondes
* un nouveau message est ajouté à la liste des messages dans un widget différent
2) Ensuite, lorsque vous cliquez sur le lien du message d'état, il devrait ouvrir le message en tant que pop-up avec des détails

Ainsi, lorsque nous essayons d'automatiser le scénario, nous sommes en mesure de soumettre le formulaire. Mais lorsque nous essayons de cliquer sur le lien du message d'état, nous devons avoir browser.ignoreSynchronization = true; car le lien est attaché à $ timeout.

Le lien est cliquable, mais un message qui s'ouvre sous forme de fenêtre contextuelle ne fonctionne pas.

Lorsque je me suis enregistré dans Stackoverflow et que certaines réponses Google ont été apportées, j'ai appris que c'était à cause du $ timeout. J'ai donc vérifié en supprimant $ timeout pour l'élément, maintenant cela fonctionne correctement.

Ma demande est donc à chaque fois que nous ne pouvons pas avoir d'élément sans $ timeout. Veuillez donc considérer le scénario ci-dessus et avoir une nouvelle fonctionnalité où le rapporteur peut ignorer l'attente de $ timeout.

Face au même problème, a dû utiliser window.setTimeout et window.clearTimeout pour l'éviter. Pas très angulaire et peut interférer avec le digest, mais $ intervalle et ignorer la synchronisation ne sont pas une solution

Face exactement au même problème, est-ce que quelqu'un de l'équipe de rapporteur répondra à cela?

+1

Cela me rendait fou (rapporteur noob ici) jusqu'à ce que j'arrive à une solution sale comme ci-dessous:

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

Fonctionne bien et n'est pas trop lent sur un navigateur sans tête.

+1

Également en train de rencontrer un problème ici. Mes tests se sont bien déroulés jusqu'à la visite d'une page qui fait quelques requêtes d'API et ouvre quelques WebSockets. À ce stade, Protractor expire à chaque fois. Je n'ai pas trouvé de bonne solution.

Je suis curieux de connaître @floribon vos méthodes d'assistance suggérées si vous désactivez browser.ignoreSynchronization = true; globalement. En ce moment, j'utilise des objets de page, qui ont tendance à ressembler à:

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 mes assistants ressemblent à ceci:

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

Avec par exemple wait.forElement retournant

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

L'idée est d'imiter le comportement réel des utilisateurs: je clique sur un élément, j'attends qu'un autre soit visible, puis je clique dessus, etc. De cette façon pas besoin d'attendre $ timeout, c'est plus rapide et plus sûr.

La clé ici est d'ignorer les erreurs de la promesse intérieure et de lui donner quelques essais supplémentaires jusqu'à ce que le délai d'attente soit atteint.

PS: Vous pouvez également utiliser ExpectedConditions pour cela mais j'ai eu quelques problèmes avec eux lorsque angular reconstruisait le nœud DOM (par exemple ng-if conditions):

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

@floribon génial, merci!

@juliemr pouvez-vous m'aider, s'il vous plaît, à attraper les messages d'erreur toast dans le rapporteur.Pls répondent sur mon identifiant de messagerie anjali. [email protected]

@juliemr L' utilisation du service $ interval pour appeler des requêtes HTTP de longue durée ne fonctionne pas. Le rapporteur a encore un délai d'expiration.
Voici mon code angulaire
this. $ intervalle (() => this. $ http.get(this.prefix (url), config), 0, 1)
rapporteur attend toujours la fin de la tâche $ http.
J'utilise le rapporteur 5.x et angualrjs 1.6x.
Pourriez-vous m'aider?

+1, quelque chose devrait être ajouté pour gérer cela.

+1

+1

+1

+1

+1

+1

Cette spécification a fonctionné pour moi. Grand merci! et bravo à

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

les gars comment je peux ajouter un délai d'expiration pour un certain cas de test
»
it ('1-devrait se connecter avec un code d'enregistrement envoyé à un e-mail', function (done) {

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


    });`
Cette page vous a été utile?
0 / 5 - 0 notes