Protractor: Prises Web

Créé le 24 mai 2017  ·  15Commentaires  ·  Source: angular/protractor

Impossible d'exécuter le test Protractor sur les applications Angular2+ à l'aide de Firrebase.
Suite de https://github.com/angular/angularfire2/issues/779

Rapport d'erreur

  • Version du nœud : v7.3.0
  • Version rapporteur : 5.1.2
  • Version angulaire : 4.1.2
  • Navigateur(s) : chrome
  • Système d'exploitation et version MacOS Sierra 10.12.4
  • Votre fichier de configuration de rapporteur
const { SpecReporter } = require('jasmine-spec-reporter');

exports.config = {
  specs: [
    './e2e/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    print: function() {}
  },
  beforeLaunch: function() {
    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });
  },
  onPrepare() {
    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
  }
};
  • Un exemple de test pertinent
import { browser, element, by } from 'protractor';

describe('test App', () => {
  beforeEach(() => {
    browser.get('/');
  });

  it('should display the home page', () => {
    expect<any>(element(by.css('app-root h1')).getText()).toBe('Test App');
  });
});
  • Résultat de l'exécution du test
- Failed: Timed out waiting for asynchronous Angular tasks to finish after 11 seconds. This may be because the current page is not an Angular application. Please see the FAQ for more details: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular
While waiting for element with locator - Locator: By(css selector, app-root h1)
  • Étapes pour reproduire le bogue
    Le simple fait d'inclure AngularFireAuth ou AngularFireDatabase dans un Angular4 AppTestComponent déclenche l'erreur.
import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import { Observable } from 'rxjs/Observable';
import * as firebase from 'firebase/app';

@Injectable()
export class FirebaseService {
  constructor(
    private afAuth: AngularFireAuth,
    private db: AngularFireDatabase,
  ) { }

Commentaire le plus utile

J'ai également dû appeler browser.waitForAngularEnabled(false); pour que les tests de rapporteur s'exécutent sans obtenir la même erreur : Failed: Timed out waiting for asynchronous Angular tasks to finish after 11 seconds...

Tous les 15 commentaires

Oui, en effet, les sockets Web sont le problème. J'ai dû abandonner le rapporteur dans mes tests en tant que cadre principal à cause du protocole ws.
+1

Très intéressant; L'un de vous pourrait-il produire un petit dépôt de test reproduisant ce problème ? Nous pouvons ensuite l'utiliser pour explorer certaines solutions ici. Merci!

@NickTomlin , j'ai réussi à reproduire le bogue dans l'un de mes projets en cours hébergé sur github. Je devais juste créer une branche distincte appelée feature/protractor-bug que vous pouvez trouver ici : (https://github.com/andreasonny83/online-board/tree/feature/protractor-bug).
Il y a une ligne commentée que vous pouvez voir ici (https://github.com/andreasonny83/online-board/blob/feature/protractor-bug/e2e/app.utils.ts#L7). Basculant le browser.ignoreSynchronization sur vrai, permettez-moi d'exécuter les tests du rapporteur. Les tests fonctionnent si je supprime complètement Firebase de mon projet.
Faites-moi savoir si vous trouvez une solution à cela.
Merci.

J'ai également dû appeler browser.waitForAngularEnabled(false); pour que les tests de rapporteur s'exécutent sans obtenir la même erreur : Failed: Timed out waiting for asynchronous Angular tasks to finish after 11 seconds...

J'ai un problème très similaire. Mes tests n'échouent pas, mais après chaque chargement de page, le test attend environ 30 secondes avant de se poursuivre (semble être un délai d'attente). Voir mon numéro #4316

Lorsque je supprime Firebase, tout va bien.

La question est de savoir comment résoudre ce problème si vous ne pouvez pas supprimer Firebase du projet.
Existe-t-il un moyen de dire au rapporteur d'ignorer HTTP avec le code d'état 101 ?

Dans mon projet, je l'ai résolu en désactivant la synchronisation. Mais cela a un inconvénient : j'ai dû insérer quelques sommeils pour attendre "manuellement" que la page soit prête. Surtout avec Angular Material que j'utilise dans mon projet car il utilise beaucoup d'animations. Et là, je dois souvent attendre que l'animation soit terminée. Mais pour moi ce n'est pas un gros problème.

Curieux de savoir si quelqu'un a eu de la chance avec ça? J'aime bien la suggestion de @Zuse .

Pour moi, cette solution de contournement a fonctionné :

    it('test ', function(){
        page.navigateTo();
        browser.sleep(3000);
        browser.waitForAngularEnabled(false);

        page.getProjectTitle().then(function(str){expect(str).toBe("Title")});

    });

Je n'ai jamais utilisé Firebase, donc je ne sais pas comment cela pourrait être mis en œuvre dans ce cas particulier, mais j'ai pensé publier mes conclusions de toute façon car cela pourrait donner des indications sur une solution possible.

J'ai eu des problèmes similaires avec d'autres services basés sur des sockets Web et la façon dont je l'ai contourné était d'utiliser NgZone pour initialiser le socket Web en dehors d'Angular.

En gros, l'idée est la suivante :

@Injectable()
export class SomeService {

  constructor(
    private ngZone: NgZone
  ) {
    this.ngZone.runOutsideAngular(() => {
      let foo = initWebSocket(...); // Whatever call may set up the web sockets based service, run it outside Angular
      foo.listen('event', e => {
        this.ngZone.run(() => {
          handleEvent(e); // Run callbacks from the web sockets service inside Angular
        });
      });
    });
  }

}

L'utilisation de cette solution m'a permis d'exécuter correctement des tests de rapporteur avec des sockets Web liés à Pusher.

J'espère que cela peut aider.

J'ai essayé l'approche de @glebihan en utilisant Injector (c'est moche :smirk:). Les tests E2E pour la connexion/déconnexion fonctionnent. Malheureusement, les tests où je liste mes articles échouent avec la fameuse exception ci-dessous même si les articles sont chargés et affichés correctement.
Failed: Timed out waiting for asynchronous Angular tasks to finish after 11 seconds. This may be because the current page is not an Angular application. Please see the FAQ for more details: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular While waiting for element with locator - Locator: By(css selector, li)

J'ai essayé de "rattacher" explicitement l'abonnement à la détection de changement d'Angular avec l'approche de https://github.com/apollographql/apollo-angular/issues/320#issuecomment -327436087 sans aucun succès.

Pour AngularFireAuth :

public constructor(
  injector: Injector,
  zone: NgZone,
) {
  zone.runOutsideAngular(() => {
    this.afAuth = injector.get(AngularFireAuth);
  });
}

Pour AngularFireDatabase :

public constructor(
  injector: Injector,
  zone: NgZone,
) {
  zone.runOutsideAngular(() => {
    this.db = injector.get(AngularFireDatabase);
  });

  this.items = this.db
      .list<Item>('/items')
      .valueChanges();
}

@PhilippeMorier Ce que j'essaierais pour votre exemple AngularFireDatabase est le suivant.

Déclarez d'abord this.items en tant que Subject ou BehaviorSubject puis alimentez-le à partir d'un abonnement qui appelle ngZone.run :

public constructor(
  injector: Injector,
  zone: NgZone,
) {
  zone.runOutsideAngular(() => {
    this.db = injector.get(AngularFireDatabase);
    this.db.list<Item>('/items').valueChanges().subscribe(i => {
      this.ngZone.run(() => {
        this.items.next(i);
      });
    });
  });
}

Des mises à jour sur ce problème ?
J'ai un problème avec la bibliothèque aspnet/signalr. Bien que j'aie encapsulé du code qui ouvre une connexion dans zone. runOutsideAngular, le rapporteur reste bloqué jusqu'à ce que la connexion Websoket se ferme.

this.zone.runOutsideAngular(() => {
   this.started = this.connection
        .start()
        .then(_ => {
            console.log('[signaler] started');
            this.isStarted = true;
            this.connection.on(
                'notify',
                (messageType: string, signal: Signal) => {
                    signal.messageType = messageType;
                    this.zone.runGuarded(() => this.signalsSubject$.next(signal));
                }
            );
        })
        .catch();
});

@pavelpykhtin c'est ma solution (avec l'aide de StackOverflow, bien sûr), pour que l'application et les tests fonctionnent comme prévu. Le consommateur de service n'a pas à gérer les zones, tout est fait à l'intérieur du service.

import { Injectable, NgZone } from "@angular/core";
import { Observable, Observer, TeardownLogic, SchedulerLike, Subscription, asapScheduler, from, of as observableOf } from "rxjs";
import { switchMap, observeOn } from "rxjs/operators";
import * as signalR from "@aspnet/signalr";

export class TheResponse {
}

class LeaveZoneScheduler implements SchedulerLike {
    constructor(
        private zone: NgZone,
        private scheduler: SchedulerLike,
    ) {
    }

    schedule(...args: any[]): Subscription {
        return this.zone.runOutsideAngular(() =>
            this.scheduler.schedule.apply(this.scheduler, args)
        );
    }

    now(): number {
        return this.scheduler.now();
    }
}

export function leaveZone(zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
    return new LeaveZoneScheduler(zone, scheduler);
}

class EnterZoneScheduler implements SchedulerLike {
    constructor(
        private zone: NgZone,
        private scheduler: SchedulerLike,
    ) {
    }

    schedule(...args: any[]): Subscription {
        return this.zone.run(() =>
            this.scheduler.schedule.apply(this.scheduler, args)
        );
    }

    now(): number {
        return this.scheduler.now();
    }
}

export function enterZone(zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
    return new EnterZoneScheduler(zone, scheduler);
}


@Injectable()
export class TheHubService {
    private static _hubConnection: signalR.HubConnection = null;
    private static _huburl: string = "/api/The/Hub";

    constructor(private _zone: NgZone) {
    }

    private static async waitForHubConnectionAsync(): Promise<signalR.HubConnection> {
        if (TheHubService._hubConnection === null) {
            TheHubService._hubConnection = new signalR.HubConnectionBuilder()
                .withUrl(TheHubService._huburl)
                .build();
            await TheHubService._hubConnection
                .start()
                .catch((err: any) => {
                    console.error("error:", err);
                });
        }

        let retry: number = 100; // 100 * 100ms = 10s
        while ((retry > 0) && (TheHubService._hubConnection.state !== signalR.HubConnectionState.Connected)) {
            await new Promise<void>(resolve => setTimeout(resolve, 100));
            retry--;
        }

        if (TheHubService._hubConnection.state !== signalR.HubConnectionState.Connected) {
            throw new Error("can't connect to the hub.");
        }

        return TheHubService._hubConnection;
    }

    getTheResponse(): Observable<TheResponse> {
        return observableOf(true, leaveZone(this._zone, asapScheduler))
            .pipe(
                switchMap((_) => from(TheHubService.waitForHubConnectionAsync())),
                switchMap((connection: signalR.HubConnection) => Observable.create((observer: Observer<TheResponse>): TeardownLogic => {
                    const streamResult: signalR.IStreamResult<TheResponse> = connection.stream("GetTheResponse");
                    const subscription = streamResult.subscribe(observer);
                    return () => subscription.dispose();
                }) as Observable<TheResponse>),
                observeOn(enterZone(this._zone, asapScheduler)),
            );
    }
}

J'ai enquêté sur un problème similaire avec nos tests Protractor et la configuration de notre websocket en dehors de la zone angulaire n'a rien changé, donc après avoir suivi les étapes de https://stackoverflow.com/questions/48627074/how-to- track-which-async-tasks-protractor-is-waiting-on pour ajouter le suivi des tâches à zone-evergreen.js J'ai trouvé que le coupable était Pace appelant setTimeout dans une boucle.

Lors de l'exécution du test en mode non headless, la saisie de Pace.stop() dans la console du navigateur (pendant que le test était suspendu, après avoir défini un délai d'attente plus long au préalable) rendait toujours le test terminé immédiatement avec succès.

https://github.com/HubSpot/pace/blob/master/pace.coffee montre que la valeur par défaut pour trackWebSockets est true. La modification de cette configuration a résolu le problème et tous nos tests se terminent maintenant avec succès !

Voici le correctif que vous pouvez appliquer en exécutant patch -d ./node_modules/zone.js/dist/ -i ../../../zone-evergreen_debug.patch dans le dossier racine de votre projet :

--- zone-evergreen.js   2020-02-07 14:46:11.000000000 -0500
+++ zone-evergreen.js   2020-02-07 14:47:34.000000000 -0500
@@ -46,6 +46,8 @@
             this._properties = zoneSpec && zoneSpec.properties || {};
             this._zoneDelegate =
                 new ZoneDelegate(this, this._parent && this._parent._zoneDelegate, zoneSpec);
+            this._taskCount = 0
+            this._tasks = {}
         }
         static assertZonePatched() {
             if (global['Promise'] !== patches['ZoneAwarePromise']) {
@@ -258,7 +260,11 @@
         _updateTaskCount(task, count) {
             const zoneDelegates = task._zoneDelegates;
             if (count == -1) {
+                delete this._tasks[task.id]
                 task._zoneDelegates = null;
+            } else {
+                task.id = this._taskCount++;
+                this._tasks[task.id] = task;
             }
             for (let i = 0; i < zoneDelegates.length; i++) {
                 zoneDelegates[i]._updateTaskCount(task.type, count);
Cette page vous a été utile?
0 / 5 - 0 notes