Protractor: Le rapporteur échoue lorsque l'application est amorcée avec ngUpgrade

Créé le 16 mai 2017  ·  51Commentaires  ·  Source: angular/protractor

Je travaille sur la migration d'une application AngularJS (1.6) vers Angular (4) et j'ai maintenant une application hybride, amorcée avec NgUpgrade. Cela semble avoir complètement cassé mes tests de rapporteur.

Pardonnez-moi si c'est juste quelque chose que je fais mal, mais je ne trouve aucune réponse quant à la raison pour laquelle cela ne fonctionnera pas.

L'erreur que j'obtiens est :

Échec : a expiré l'attente de la fin des tâches angulaires asynchrones après 11 secondes. Cela peut être dû au fait que la page actuelle n'est pas une application angulaire. Veuillez consulter la FAQ pour plus de détails : https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting -for-angular

En attendant l'élément avec le localisateur - Localisateur : par (sélecteur css, .toggle-summary-button)

Modifications apportées aux applications hybrides

L'application semble fonctionner correctement et les composants AngularJS et Angular fonctionnent comme prévu. Les modifications que j'ai apportées au processus d'amorçage sont :

1 Suppression de ng-app de la balise html :

<html lang="en" *ng-app="myapp">

2 Ajout d'un AppModules (@NgModule) etc.

3 Utilisation de NgUpgrade pour amorcer l'application :

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
  const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
  upgrade.bootstrap(document.body, ['myapp'], {strictDi: true});
});

Tests de rapporteur

Sur la base de l'erreur ci-dessus, le problème semble être lié à ce que Protractor fait lorsqu'il attend Angular. J'ai un bloc beforeEach qui charge la page de connexion, remplit les détails et se connecte. Bizarrement, il ouvre toujours la page et saisit du texte dans le champ du nom d'utilisateur, mais il ne parvient pas à aller plus loin.

J'ai essayé, sans succès :

  • ajouter "rootElement: 'body'" à mon fichier de configuration de rapporteur
  • ajoutant "ng12Hybrid: true" à mon fichier de configuration de rapporteur - je reçois un message disant qu'il ne devrait plus être nécessaire car il détecte automatiquement.
  • en augmentant le paramètre allScriptsTimeout de 11000 à 60000 et il expire toujours.
  • désactiver le paramètre waitForAngularEnabled. Cela résout le problème avec les champs de connexion, mais aucune de mes simulations http ne fonctionne et les tests échouent.

Commentaire le plus utile

Salut - nous travaillons sur un changement pour vous permettre de configurer les tâches que Protractor attend, ce qui peut vous aider à gérer le problème ci-dessus. Il s'agit d'un vaste changement impliquant Zone.js et Angular et n'ayant pas d'ETA spécifique (ce sera de l'ordre de quelques semaines). Mettra à jour sur les progrès ici.

Tous les 51 commentaires

J'ai eu un problème similaire qui a été résolu en enveloppant ce code de mise à niveau comme ça :

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app.module';

angular.element(document).ready(() => {
  platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
    const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
    upgrade.bootstrap(document.body, ['myapp'], {strictDi: true});
  });
});

Et dans la configuration, je n'utilise aucun de ceux-ci :

//ng12Hybrid: true,
//useAllAngular2AppRoots: true,
//rootElement: 'body',

Pourriez-vous jeter un œil à https://github.com/angular/protractor/issues/4234 et voir si vous rencontrez le même problème ? De plus, dans ngUpgrade, $interval bloquera désormais Protractor là où il ne l'était pas auparavant (https://github.com/angular/angular/issues/16349), mais j'y travaille.

@Evilweed - merci pour la suggestion, malheureusement, cela ne semble pas le résoudre pour moi.

@heathkit - J'ai trouvé un intervalle $ de longue durée en recherchant l'ensemble du projet, mais même après l'avoir supprimé, le problème persiste. J'ai également commenté toutes les utilisations de $interval ou $timeout dans l'ensemble de l'application et cela échoue toujours avec la même erreur. Savez-vous s'il existe des moyens de déboguer et de découvrir ce qui pourrait bloquer s'il s'agit d'un problème avec quelque chose d'asynchrone ?

Pouvez-vous poster votre erreur complète? Il est censé montrer quelles tâches sont en attente dans le message d'erreur, mais il est possible qu'il en manque certaines. Par exemple, l'erreur n'affichera pas encore les tâches en attente du côté angulaire. Si vous avez des tâches asynchrones de longue durée dans la partie angulaire de votre application, vous devrez les exécuter en dehors de la zone angulaire pour éviter de bloquer Protractor. (Voir https://github.com/angular/protractor/blob/master/docs/timeouts.md)

Merci encore d'avoir répondu. Le détail de l'erreur est ci-dessous. Quelques points à noter :

  • Mon fichier de test contient un bloc beforeAll qui est censé remplir les champs nom d'utilisateur et mot de passe pour se connecter. Il remplit le champ du nom d'utilisateur avec succès, mais expire ensuite dans waitForAngular et ne remplit pas le champ du mot de passe. C'est ce que semble être la première moitié de l'erreur. La seconde moitié est le test d'échec réel (ce qui est logique, car il n'a pas réussi à se connecter, donc l'élément n'est pas présent).

  • L'erreur suggère que le problème peut provenir d'un appel HTTP, donc peut-être pas $interval ou $timeout.

  • Je n'ai pas grand-chose du côté d'Angular pour le moment, c'est presque toujours AngularJS. Il ne va pas jusqu'à afficher le nouveau composant Angular car il ne parvient pas à se connecter (donc je doute que l'appel http soit lié au composant Angular 4)

  Message:
    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, *[name="password"])
  Stack:
    ScriptTimeoutError: asynchronous script timeout: result was not received in 11 seconds
      (Session info: chrome=58.0.3029.110)
      (Driver info: chromedriver=2.29.461585,platform=Mac OS X 10.12.4 x86_64) (WARNING: The server did not provide any stacktrace information)
    Command duration or timeout: 11.07 seconds
    Build info: version: '3.4.0', revision: 'unknown', time: 'unknown'
    System info: host: 'Matt.lan', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.12.4', java.version: '1.8.0_111'
    Driver info: org.openqa.selenium.chrome.ChromeDriver
    Capabilities [{applicationCacheEnabled=false, rotatable=false, mobileEmulationEnabled=false, networkConnectionEnabled=false, chrome={chromedriverVersion=2.29.461585, takesHeapSnapshot=true, pageLoadStrategy=normal, databaseEnabled=false, handlesAlerts=true, hasTouchScreen=false, version=58.0.3029.110, platform=MAC, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true, unexpectedAlertBehaviour=}]
    Session ID: 40deafceb49b72b1c0188ad0895c1179
        at Object.checkLegacyResponse (/Users/matt/web-app/node_modules/selenium-webdriver/lib/error.js:505:15)
        at parseHttpResponse (/Users/matt/web-app/node_modules/selenium-webdriver/lib/http.js:509:13)
        at doSend.then.response (/Users/matt/web-app/node_modules/selenium-webdriver/lib/http.js:440:13)
        at process._tickCallback (internal/process/next_tick.js:109:7)
    From: Task: Protractor.waitForAngular() - Locator: By(css selector, *[name="password"])
        at thenableWebDriverProxy.schedule (/Users/matt/web-app/node_modules/selenium-webdriver/lib/webdriver.js:816:17)
        at ProtractorBrowser.executeAsyncScript_ (/Users/matt/web-app/node_modules/protractor/lib/browser.ts:608:24)
        at angularAppRoot.then (/Users/matt/web-app/node_modules/protractor/lib/browser.ts:642:23)
        at ManagedPromise.invokeCallback_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1366:14)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
        at TaskQueue.executeNext_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2953:27)
        at asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2813:27)
        at /Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:676:7
        at process._tickCallback (internal/process/next_tick.js:109:7)Error
        at ElementArrayFinder.applyAction_ (/Users/matt/web-app/node_modules/protractor/lib/element.ts:482:23)
        at ElementArrayFinder.(anonymous function).args [as sendKeys] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:96:21)
        at ElementFinder.(anonymous function).args [as sendKeys] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:873:14)
        at /Users/matt/web-app/e2e-tests/modules/authentication.js:83:38
        at ManagedPromise.invokeCallback_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1366:14)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
        at TaskQueue.executeNext_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2953:27)
        at asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2813:27)
        at /Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:676:7
        at process._tickCallback (internal/process/next_tick.js:109:7)
    From: Task: Run beforeAll in control flow
        at Object.<anonymous> (/Users/matt/web-app/node_modules/jasminewd2/index.js:94:19)
    From asynchronous test: 
    Error
        at Suite.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:28:3)
        at Object.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:7:1)
        at Module._compile (module.js:571:32)
        at Object.Module._extensions..js (module.js:580:10)
        at Module.load (module.js:488:32)
        at tryModuleLoad (module.js:447:12)
  Message:
    Failed: No element found using locator: By(css selector, .toggle-asset-summary-button)
  Stack:
    NoSuchElementError: No element found using locator: By(css selector, .toggle-asset-summary-button)
        at elementArrayFinder.getWebElements.then (/Users/matt/web-app/node_modules/protractor/lib/element.ts:851:17)
        at ManagedPromise.invokeCallback_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1366:14)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
        at TaskQueue.executeNext_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2953:27)
        at asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2813:27)
        at /Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:676:7
        at process._tickCallback (internal/process/next_tick.js:109:7)Error
        at ElementArrayFinder.applyAction_ (/Users/matt/web-app/node_modules/protractor/lib/element.ts:482:23)
        at ElementArrayFinder.(anonymous function).args [as click] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:96:21)
        at ElementFinder.(anonymous function).args [as click] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:873:14)
        at Page.showAssetSummaryComponent (/Users/matt/web-app/e2e-tests/specs/home/page-object.js:289:31)
        at Object.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:63:12)
        at /Users/matt/web-app/node_modules/jasminewd2/index.js:112:25
        at new ManagedPromise (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1067:7)
        at ControlFlow.promise (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2396:12)
        at schedulerExecute (/Users/matt/web-app/node_modules/jasminewd2/index.js:95:18)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
    From: Task: Run fit("should open asset summary component") in control flow
        at Object.<anonymous> (/Users/matt/web-app/node_modules/jasminewd2/index.js:94:19)
        at /Users/matt/web-app/node_modules/jasminewd2/index.js:64:48
        at ControlFlow.emit (/Users/matt/web-app/node_modules/selenium-webdriver/lib/events.js:62:21)
        at ControlFlow.shutdown_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2565:10)
        at shutdownTask_.MicroTask (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2490:53)
        at MicroTask.asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2619:9)
    From asynchronous test: 
    Error
        at Suite.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:62:5)
        at Suite.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:60:3)
        at Object.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:7:1)

Exactement la même chose - avec une nouvelle configuration angular4 "quickstart" et un test de rapporteur très basique. Tout fonctionne bien en mode non hybride.

Failed: Timed out waiting for asynchronous Angular tasks to finish after 50 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, #loginModal h4)

Je vois toujours ce problème.
Utilisation de : @angular/upgrade 4.2.2
qui contient le correctif de @heathkit (https://github.com/angular/angular/pull/16836) pour obtenir l'api de testabilité stable, mais cela ne semble toujours pas fonctionner

Si quelqu'un pouvait partager un repo qui montre le problème, je l'examinerai plus avant.

Ce que j'ai découvert jusqu'à présent

Après quelques recherches, j'ai découvert l'une des causes du délai d'attente que je vois (ou l'une d'entre elles, je soupçonne que ce n'est pas la seule). Il s'avère que l'une des dépendances que j'utilise appelle window.setTimeout() ce qui empêche Protractor de se synchroniser (dans mon cas, c'est
moment angulaire .

Cela fonctionnait bien lorsque mon application exécutait uniquement AngularJS, mais provoquait un problème de délai d'attente lors de l'amorçage en tant qu'application hybride (vraisemblablement en raison de la façon dont elle est exécutée dans les zones).

Exemple de pension

J'ai créé un exemple de dépôt sur https://github.com/mattwilson1024/angular-hybrid . C'est une application hybride (NgUpgrade) avec les parties suivantes :

  • La page la plus à l'extérieur (la CharactersPage) est fournie par AngularJS (1.6).
  • La page contient CharacterListComponent, qui est un composant Angular (4) rétrogradé.
  • Le CharacterListComponent utilise un autre composant Angular 4, le composant CharacterDetail.

La branche principale contient l'application de travail et un seul testeur de rapporteur qui ne passe aucun problème.

La branche angular-moment ajoute une dépendance à la bibliothèque angular-moment et utilise sa directive am-time-ago pour ajouter une étiquette à la page indiquant combien d'années il y a depuis le premier épisode. L'application elle-même fonctionne correctement, mais le test du rapporteur ne parvient plus à se synchroniser.

Délais d'attente

Pour confirmer que cela se produit, j'ai essayé d'ajouter différents types de délai d'attente au contrôleur AngularJS, avec les résultats suivants. Cela a confirmé que $interval fonctionne, mais pas $timeout, setInterval ou setTimeout.

function sayHello() { console.log('Hello, world'); }

$interval(sayHello, 30000); // Succeeds (with Angular 4.2.0 and later - see https://github.com/angular/angular/pull/16836)
$timeout(sayHello, 30000); // Fails - Timed out waiting for asynchronous Angular tasks to finish after 11 seconds
setInterval(sayHello, 30000); // Fails - Timed out waiting for asynchronous Angular tasks to finish after 11 seconds
setTimeout(sayHello, 300000); // Fails - Timed out waiting for asynchronous Angular tasks to finish after 11 seconds

Prochaines étapes

Ayant réussi à traquer ce qui se passe, je ne sais toujours pas quelle est la solution. Je comprends qu'il peut être nécessaire d'exécuter certains morceaux de code "en dehors de la zone angulaire", bien que je ne sache pas comment le faire à partir de segments AngularJS où je n'ai pas de classe TypeScript dans laquelle injecter la zone, etc.

Même alors, si le code du problème se produit dans une bibliothèque tierce (comme c'est le cas dans mon exemple avec angular-moment) - quelle serait l'approche pour contourner ce problème pour que les tests fonctionnent comme ils le faisaient avant la mise à niveau vers un configuration hybride ?

Les zones sont définies sur la fenêtre, vous n'avez donc pas besoin d'injecter NgZone. Vous pouvez courir en dehors de la zone angulaire dans AngularJS comme ceci

if (Zone && Zone.current.name === 'angular') {
  let angularZone = Zone.current;
  Zone.parent.run(() => {
    $timeout(() => {
       // Stuff here is run outside the Angular zone and will not be change detected
       angularZone.run(() => {
         // Inside the zone and will be change detected.
       });
    }, 500);
  });
}

Vous avez raison de dire que toute utilisation de setTimeout ou setInterval, même dans du code tiers, peut bloquer la stabilité du rapporteur et provoquer des délais d'attente. Nous ajoutons le suivi des tâches à Angular et AngularJS qui devrait faciliter le débogage de ces problèmes en vous montrant d'où vient la tâche asynchrone sur laquelle Protractor a expiré. En réalité, il faudra quelques mois avant que nous puissions intégrer cela dans Protractor.

En ce qui concerne le moment angulaire, votre meilleure option pourrait être de leur soumettre un PR le rendant conscient de la zone, comme indiqué ci-dessus. Sinon, vous pouvez désactiver sélectivement waitForAngular et utiliser à la place browser.wait et ExpectedConditions. Nous travaillons sur une API waitForAngular plus flexible qui vous permettra de contrôler les tâches que vous attendez, mais c'est encore loin.

Dans notre situation, les tests peuvent être écrits de manière générique (sans synchronisation angulaire) ( browser.ignoreSynchronization = true utilisant des EC, etc.), mais échouent lors de la définition de browser.ignoreSynchronization = false; avec "Error while waiting for Protractor to sync with the page: "Cannot read property '$$testability' of undefined"

Cela annule fondamentalement toute notre suite de tests automatisés E2E.

En utilisant Protractor 5.1.2, si je définis ng12Hybrid: true j'obtiens le message :

Vous avez défini ng12Hybrid. Depuis Protractor 4.1.0, Protractor peut automatiquement déduire si vous utilisez une application ngUpgrade (tant que ng1 est chargé avant d'appeler platformBrowserDynamic()), et cet indicateur n'est plus nécessaire pour la plupart des utilisateurs

Alors évidemment la documentation angular.io est obsolète ici : https://angular.io/guide/upgrade#e2e -tests

Derniers mots célèbres du même document :

Les tests E2E ne concernent pas vraiment la structure interne des composants de l'application. Cela signifie également que, même si vous modifiez un peu le projet pendant la mise à niveau, la suite de tests E2E devrait continuer à réussir avec quelques modifications mineures. Vous n'avez pas modifié le comportement de l'application du point de vue de l'utilisateur.

À la lumière de cette déclaration sur angular.io :

@heathkit Merci pour le conseil sur l'exécution à l'intérieur et à l'extérieur des zones, mais pour être honnête, nous recherchons une solution qui fonctionne avec notre code AngularJS existant, tel quel. Nous avons une vaste suite Protractor qui repose sur la synchronisation implicite d'AngularJS. Notre objectif est de migrer notre AngularJS vers Angular à l'aide de ngUpgrade, mais ce problème de ne pas pouvoir exécuter notre suite Protractor existante en mode de mise à niveau hybride a contrecarré notre initiative. Une solution judicieuse serait la bienvenue en ce moment.

Si nous faisons quelque chose de fondamentalement mal, nous aimerions bien savoir quoi.

Face au même problème ! :0

Voir la même chose ici : AngularJS 1.6 -> Angular 4.2.4

Quelqu'un connaît-il une solution de contournement, quelle que soit sa taille ? Sans revenir à 1.6 c'est-à-dire.

@quinw68 - @heathkit suggère une méthode pour exécuter le code en dehors de la 'Zone' angulaire dans sa réponse précédente dans ce fil. Je pense que cela signifie des modifications du code source de votre application pour la faire fonctionner, et peut même dépendre de la prise en charge de bibliothèques tierces. Corrigez-moi si j'ai tort, s'il-vous plait.

Une autre "solution de contournement" dont je suis conscient consiste à traiter votre application existante comme s'il ne s'agissait plus d'une application AngularJS. Cela signifie que vous devrez modifier vos tests Protractor afin qu'ils n'attendent plus intrinsèquement Angular, mais vérifier explicitement la présence d'éléments sur la page avant de les interroger sur leur état. Selon la taille de votre application, cela peut être une tâche ardue.

J'espère que cela pourra aider.

Pour autant que je comprends le problème, ce n'est pas spécifique à ngUpgrade, mais plutôt spécifique à Angular. Je veux dire, même dans une application Angular (2+) pure, avoir un long setTimeout (soit directement, soit via une bibliothèque tierce) entraînera l'expiration du rapporteur.

@heathkit , pouvez-vous le confirmer ou ai-je raté quelque chose ? ngUpgrade désactive-t-il Protractor ou aggrave-t-il les choses d'une manière ou d'une autre ?

@gkalpak Vous avez raison de dire qu'il s'agit davantage d'un problème spécifique à Angular, mais les gens le rencontrent lorsqu'ils utilisent ngUpgrade avec une grande application AngularJS.

Dans AngularJS, Protractor n'attendrait que les tâches asynchrones démarrées avec $browser.defer() - en gros, juste des appels $timeout et $http dans le code de l'application. Une fois que quelqu'un commence à utiliser ngUpgrade, ses tests Protractor existants commenceront à expirer en raison de longs appels setTimeout (potentiellement à partir de bibliothèques tierces) où ils ne l'avaient pas fait auparavant. Donc, la plupart du temps, le problème est que ngUpgrade fonctionne comme prévu, et maintenant l'application AngularJS et les tests Protractor des gens sont soudainement soumis à la sémantique Angular (2+).

Alors, comment résoudre ce problème pour une application angulaire pure ? Que ferait-on si une bibliothèque Angular tierce définissait un long setTimeout , provoquant l'expiration du délai de Protractor ?

@vikerman J'ai entendu dire que vous allez aider un nouveau membre de l'équipe à être opérationnel sur celui-ci très bientôt, alors vous l'attribuez jusqu'à ce qu'il soit à bord.

Nous n'avons pas vraiment de bonne réponse pour le cas où une application tierce définit un long délai d'attente. Pour le moment, tout ce que vous pouvez faire est de désactiver waitForAngular et de vous fier aux conditions attendues, mais ce n'est pas idéal. Je travaille sur l'ajout d'une fonctionnalité à Protractor qui donnera aux gens plus de contrôle sur les macrotâches que leurs tests attendent, mais c'est encore loin.

@heathkit @vikerman Une mise à jour sur celui-ci s'il vous plaît ?

Le projet sur lequel je travaille avait ce problème auparavant, mais ils ont enveloppé une fonction d'interrogation continue pour qu'elle s'exécute en dehors de la zone angulaire afin que cela n'affecte pas browser.waitForAngular();

Tout d'abord merci à l'équipe de rapporteurs/contributeurs . Protractor a été le fondement de mon application pour avoir très peu de problèmes de production sur des dizaines de versions sur 3 ans, plus de 200 services, directives, composants, contrôleurs avec environ 400 tests e2e Protractor.

C'est un bon rappel à quel point je compte sur Protractor, mais ce problème a arrêté ma mise à niveau angulaire dans son élan après avoir consacré 2 semaines à l'amorçage de ngUpgrade et des besoins auxiliaires. C'est fait, mais maintenant, les tests e2e ne peuvent pas être exécutés et le déploiement est arrêté sans chemin visible (encore).

Grâce aux bonnes informations de @mattwilson1024 , je me suis assuré que $timeout , setTimeout , setInterval ne sont pas utilisés dans notre application.

Mais, j'ai trouvé que angular-ui-bootstrap (v.2.5.6) et ui-select (v0.19.8) utilisent $timeout . J'utilise beaucoup les deux bibliothèques. Il semble que beaucoup d'autres s'appuient également sur eux - angular-ui-bootstrap - 400k npm downloads/mo, ui-select 100k+/mo.

angular-ui-bootstrap créé un problème en 2015 pour remplacer $timeout par $interval spécifiquement à cause de ce problème, mais a malheureusement décidé que "ce bogue devrait être corrigé dans le rapporteur et pas ici. Fermeture de ceci." De plus, angular-ui-bootstrap ne fournit plus de mises à jour dans liu de leur version angulaire @ng-bootstrap/ng-bootstrap qui est en version bêta.

Je suis surpris quand je vois des bibliothèques angulaires utilisées de manière significative qui ne garantissent pas la validité du rapporteur. Sur ce, je suis d'accord avec @heathkit que ces bibliothèques doivent se réparer. Mais j'espère que Protractor pourra intervenir et tout atténuer d'un seul coup. Même une belle solution de contournement serait d'or. Je suis déçu de devoir annuler mon ngUpgrade pour le moment jusqu'à ce que je déterre toutes ces dépendances $timeout et, éventuellement, plutôt que d'inclure ngGrade et de commencer à sélectionner des éléments à mettre à niveau lorsque cela est possible, je ont probablement une dépendance pour mettre à niveau ou migrer à partir de ces éléments tiers avec l'ajout de ngUpgrade. Pouah.

Je poursuis mes recherches sur mon application (les coupables spécifiques causant le ou les délais d'attente, comment avancer, etc.) et je rapporterai tout ce qui pourrait être utile à d'autres.

Où j'ai trouvé $timeout :
angular-ui-bootstrap v2.5.6

  • UibAlertController
  • UibCarouselController
  • UibDatepickerPopupController
  • UibTypeaheadController
  • UibTooltip

ui-select

  • uiSelect
  • uiSelectMultiple
  • uiSelectSingle
  • uiSelectSort
  • uisOuvrirFermer

angular-resource $resource injecte et utilise $timeout
filtre angulaire ( $window.setTimeout )

Salut - nous travaillons sur un changement pour vous permettre de configurer les tâches que Protractor attend, ce qui peut vous aider à gérer le problème ci-dessus. Il s'agit d'un vaste changement impliquant Zone.js et Angular et n'ayant pas d'ETA spécifique (ce sera de l'ordre de quelques semaines). Mettra à jour sur les progrès ici.

J'ai le même problème lorsque j'amorce mon application [email protected] avec NgUpgrade to [email protected].
Merci pour les commentaires ci-dessus, en particulier @mattwilson1024 et @tonybranfort , qui

J'ai trouvé un workaournd dans mon cas, en ajoutant browser.ignoreSynchronization = true/false; autour du code qui expire.

Par exemple, l'un de mes tests a effectué une simple sélection dans un groupe de boutons radio et s'attend à ce que la bonne option soit sélectionnée :

it('should be able to pick option', function () {
        expect(myPage.optionSelected()).toBe(false);
        myPage.pickOption('Option 1');
        expect(myPage.anyOptionSelected()).toBe(true);
        expect(myPage.getSelectedOption()).toBe('Option 1');
});

Lorsque j'essaie de déboguer ce code, j'ai constaté que le délai d'attente s'était produit à la dernière étape. Je suppose que le problème sous-jacent est une info-bulle de angular-ui-bootstrap, avec l'utilisation $ timeout : https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js

Cependant, il n'y a pas d'appels asynchrones dans ce test. Donc, ce que je fais, c'est simplement ajouter ignoreSynchronization au début et le réinitialiser à la fin :

it('should be able to pick a font', function () {
        browser.ignoreSynchronization = true;  // tell protractor do not waitForAngular
        expect(myPage.optionSelected()).toBe(false);
        myPage.PickOption('Option 1');
        expect(myPage.anyOptionSelected()).toBe(true);
        expect(myPage.getSelectedOption()).toBe('Option 1');
        browser.ignoreSynchronization = false;  // reset the flag
});

Je ne sais pas si c'est la méthode recommandée ou si cela rendra les tests plus flous. Mais pour les tests qui ne nécessitent aucun appel asynchrone mais qui souffrent toujours du problème de délai d'attente, cela devrait les corriger.

Salut @vikerman - Une mise à jour à ce sujet ? https://github.com/angular/protractor/issues/4290#issuecomment-337094393

@vikerman Y a-t-il une mise à jour à ce sujet ?

Quelqu'un a-t-il de bons conseils sur la façon de trouver ces délais d'attente de longue durée afin que je puisse les retirer de mon application ?

De : Castaway(s ?) sur l'île d'AngularJS échoué par le rapporteur n° 4290
À : @vikerman @juliemr

Ohé ! Pourrions-nous avoir une mise à jour sur ce problème? La dernière mise à jour le 16 octobre était un ETA dans "l'ordre des semaines". Ou, s'il vous plaît, fermez ce problème afin que nous puissions passer à autre chose.

Voici les radeaux que j'envisage actuellement :

  • Remplacez tous les angular-ui-bootstrap / ui-select . C'est important pour moi. Cela doit être fait de toute façon, mais mon souci est que quelque chose d'autre apparaisse et que je serai toujours bloqué sur AngularJS après ce travail. Leçon apprise : encapsuler des composants tiers avec mes propres composants.
  • waitForAngularEnabled(false) pour tous les tests suggérés par @vladotesanovic. Je suis sceptique à ce sujet pour une application entière mais je ne l'ai pas testée. J'utilise waitForAngularEnabled dans une seule situation maintenant et uniquement pour un cas très spécifique.
  • Mettez à niveau l'ensemble de l'application vers Angular plutôt que vers AngularJS/Angular migration incrémentielle côte à côte : non, ce n'est tout simplement pas une option avec mes ressources.
  • Migrer vers Vue ou React au lieu d'Angular. En haut de la liste, d'autant plus que les deux peuvent coexister avec AngularJs.
  • Passez à Marionnettiste et abandonnez tous les tests de rapporteur - Il s'agit probablement d'une orientation stratégique.
  • OU un correctif de #4290 et je peux décommenter ces quelques lignes nécessaires pour commencer à utiliser ngUpgrade :)

Notre équipe est en train de mettre à niveau notre application complexe et les tests de rapporteur échouent lorsqu'ils sont amorcés avec NgUpgrade. Nous avons dû revenir sur nos modifications car nous ne pouvons désormais plus traiter tous les tests qui échouent et qui sont des faux positifs.

Où en sommes-nous avec ce problème ? Je crains de devoir passer à un autre cadre de test afin que nous puissions conserver nos plans de mise à niveau d'Angular.

@tonybranfort : ui-select et angular-ui-bootstrap - c'est un obstacle majeur à notre mise à niveau. Il semble que vous ayez pris les devants sur ce problème, cependant, tenez-nous au courant ! ??

Nous avons le même problème. Quelqu'un du rapporteur peut-il nous mettre à jour à ce sujet s'il vous plaît ?

Nous sommes à mi-chemin de la mise à niveau de notre application à partir d'Angular V1 et déteste avoir cette cause est de revenir à V1 et d'abandonner tout le travail effectué pour la mise à niveau vers Angular V5 jusqu'à présent.

Jetant un autre vote ici, nous sommes en train de mettre à niveau notre application vers Angular et nous utilisons actuellement une application au coup par coup de waitForAngularEnabled(false) pour les faire passer sans mettre toute la suite en danger. C'est extraordinairement non idéal, cependant, une meilleure solution serait donc grandement appréciée.

Nous venons également de rencontrer ce problème, en particulier en ce qui concerne le service $ Interval. Parfois, le rapporteur l'attend et parfois non - je n'ai pas encore trouvé le motif.

Édition longue durée : apparemment, cela dépend du moment où l'intervalle est invoqué (dans la zone ou non)

Salut @vikerman - Des nouvelles sur les changements pour vous permettre de configurer les tâches que Protractor attend ?

Même problème ici.

@vikerman existe-t-il une mise à jour ou une solution de contournement ?

Nous rencontrons les mêmes problèmes. Ce serait formidable s'il y avait des progrès sur cette question.

Malheureusement, j'ai le même problème. Y a-t-il des progrès en vue à ce sujet ?

Nous ajoutons le suivi des tâches à Angular et AngularJS qui devrait faciliter le débogage de ces problèmes en vous montrant d'où vient la tâche asynchrone sur laquelle Protractor a expiré.

@heathkit Est-ce déjà fait quelque part ? nous avons du mal à identifier ces endroits dans notre base de code.

ce serait aussi bien d'avoir de la documentation sur ce que le rapporteur attend avec impatience. Celui-ci semble obsolète : https://www.protractortest.org/#/system -setup. il mentionne seulement de remplacer $timeout par $interval . Rien sur setTimeout ou setInterval.
quelques docs sont ici : http://www.protractortest.org/#/timeouts. mais aucun indice pour les détecter...

Je pense que cela vous aidera à faire vos tests "hybride angulaire + rapporteur". Ça m'a aidé. Vérifiez le lien : https://github.com/ui-router/angular-hybrid/issues/39#issuecomment-309057814

Nous avons un peu avancé dans la compréhension de ce qui se passe avec ce problème. Nous semblons avoir le même. Il semble que la mise à niveau du cycle watcher/digest angularjs fonctionne un peu différemment et puisse activer des boucles de cycle de digestion infinies. Nous en avons trouvé 1 avec un tiers que nous utilisions et nous étudions le prochain. Mais fondamentalement, l'observateur causait un changement à la chose regardée, provoquant le déclenchement de l'observateur, etc. Pour tester si vous rencontrez le même problème, dans Chrome, ouvrez simplement les outils de développement, accédez aux performances, enregistrez quelques secondes, et voir si vous avez beaucoup d'appels NgZone. Si vous le faites, alors quelque part dans votre code, vous avez un cycle de résumé infini. L'exploration des appels peut vous aider à déterminer ce qui en est la cause

Vous pouvez également modifier un peu le fichier zone.js local et ajouter des journaux de console où les tâches de délai d'attente/d'intervalle sont corrigées pour voir ce qui les appelle. Un moyen simple de le faire :

try {
  throw new Error("track");
} catch(err) {
  console.debug(err);
}

Edit : tellement amusant de formater du code sur mobile !

@vikerman Avez-vous des nouvelles à ce sujet ? Ce serait formidable si vous pouviez donner votre avis sur le délai de résolution de ce problème ? Merci.

Vous pouvez également modifier un peu le fichier zone.js local et ajouter des journaux de console où les tâches de délai d'attente/d'intervalle sont corrigées pour voir ce qui les appelle. Un moyen simple de le faire :

try {
  throw new Error("track");
} catch(err) {
  console.debug(err);
}

Edit : tellement amusant de formater du code sur mobile !

Où mettriez-vous ce code pour suivre les délais d'attente/intervalles ?

@maurycyg patchTimer -> la fonction

Ce fil est décourageant à voir. Y a-t-il une mise à jour à ce sujet ?

Une solution de contournement potentielle pour ne pas utiliser $timeout consiste à le remplacer par $interval (pour les bibliothèques tierces)
Le code ci-dessous a résolu le problème de délai d'attente pour moi (vérification du navigateur ajoutée au cas où). Dans mon cas, le problème était causé par des composants d'amorçage angulaire, qui appelaient infiniment $ timeout avec un délai de 0.

app.config(function($provide) {
    function isUnderProtractor() {
        var nav = window.navigator;
        var lowercasedUserAgent = nav.userAgent;
        return !lowercasedUserAgent || lowercasedUserAgent.contains('protractor');
    }

    if (isUnderE2ETest()) {
        $provide.decorator('$timeout', ['$interval', '$delegate', function($interval, $delegate) {
            var newTimeout = function(fn, delay, invokeApply, pass) {
                if (!delay)
                    return $interval(fn, delay, 1, invokeApply, pass);
                else
                    return $delegate(fn, delay, invokeApply, pass);
            };
            newTimeout.cancel = $delegate.cancel;
            return newTimeout;
        }]);
    }
}

À partir de zone.js v0.8.9, vous pouvez choisir les modules d'API Web que vous souhaitez corriger afin de réduire les frais généraux introduits par le correctif de ces modules.
https://github.com/angular/zone.js/blob/master/MODULE.md

Ajoutez le fichier zone-flags.ts avec le contenu ci-dessous
(window as any).__Zone_disable_timers = true;

Importez-le dans polyfills.ts avant zone.js

import './zone-flags';
import 'zone.js/dist/zone';  // Included with Angular CLI.

Il est apparu que la solution ci-dessus avec la désactivation des minuteurs de zone gâche les Obserables (ex. asyncValidators). La solution qui a finalement fonctionné pour moi est setTimeout de patch singe.

platformBrowserDynamic().bootstrapModule(AppModule)
.then((moduleRef: NgModuleRef<AppModule>) => {
  const ngZone: NgZone = moduleRef.injector.get(NgZone);
  const originalSetTimeout = window.setTimeout;
  (window as any).setTimeout = function setTimeout(fn, ms) {
    return ngZone.runOutsideAngular(() => {
      return originalSetTimeout(() => {
        ngZone.run(fn);
      }, ms);
    });
  };
})

Peut-être que le problème ne réside pas dans le rapporteur lui-même, mais dans le code de l'application.

Avec la transition de Angular JS qui utilise un cycle de résumé pour suivre les changements, à l'application hybride avec Angular2+ qui utilise zone.js pour détecter les changements, Protractor a commencé à tomber avec des délais d'attente.

Le rapporteur démarre l'action de test suivante uniquement lorsque l'application est stable. La stabilité dans le cas d'une application hybride signifie la stabilité de la partie Angular JS de l'application et de la partie Angular2+ de l'application. Les deux parties doivent être stables.

La stabilité angulaire de JS est définie par $$testability.whenStable . La stabilité d'Angular 2+ est déterminée par window.getAngularTestability(...).whenStable . Vous pouvez déboguer le code Protractor et voir par vous-même. Pour ce faire, ajoutez l'instruction debugger; au fichier waitForAngular _\node_modules\protractor\built\clientsidescripts.js_ ;
Vous remarquerez que la stabilité de la zone Angular2+ ne se produit jamais. Pour la stabilité , toutes les micro-tâches et macro-tâches doivent être terminées.

Maintenant, nous comprenons le problème. La partie angulaire 2+ de l'application est toujours instable et, par conséquent, Protractor attend indéfiniment jusqu'à ce qu'elle devienne stable et tombe avec les délais d'attente. La stabilité angulaire de JS dans le cas d'une application hybride ne devrait pas avoir changé et n'affecte pas les délais d'attente du rapporteur. De cela, nous pouvons conclure qu'il est nécessaire de corriger l'instabilité de la zone Angular2+, puis Protractor commencera à fonctionner comme prévu.

Première tentative de correction :
Comme il y avait peu de temps et que je voulais tout réparer d'un coup, j'ai écrit du code qui a sorti la planification des tâches de la zone Angular2+.

    $provide.decorator("$animateCss", ["$delegate", createDecorator]);
    $provide.decorator("$http", ["$delegate", createDecorator]);
    $provide.decorator("$timeout", ["$delegate", createDecorator]);
    $provide.decorator("$interval", ["$delegate", createDecorator]);
    $provide.decorator("$q", ["$delegate", createDecorator]);
    $provide.decorator("$xhrFactory", ["$delegate", createDecorator]);
    $provide.decorator("$templateRequest", ["$delegate", createDecorator]);
    $provide.decorator("$httpBackend", ["$delegate", createDecorator]);

    $provide.decorator("$animate", ["$delegate", createDecorator]);
    $provide.decorator("$rootScope", ["$delegate", createDecorator]);
    $provide.decorator("$templateCache", ["$delegate", createDecorator]);
    $provide.decorator("$browser", ["$delegate", createDecorator]);

    function runInRootZone(fn, ...args) {
        if (window.Zone.current.parent) {
            return window.Zone.current.parent.run(() => runInRootZone(fn, ...args));
        }

        return fn(...args);
    }

    function decorate(decorator, $delegate) {

        const keys = Object.keys($delegate);
        for (let i = 0; i < keys.length; i++) {

            const key = keys[i];
            if (angular.isFunction($delegate[key])) {
                decorator[key] = createDecoratorForFunctionLikeService($delegate[key]);
            } else {
                if (decorator[key] !== $delegate[key]) {
                    decorator[key] = $delegate[key];
                }
            }
        }
    }

    function createDecoratorForFunctionLikeService($delegate) {

        function decorator(...args) {
            return runInRootZone($delegate, ...args);
        }

        decorate(decorator, $delegate);

        return decorator;
    }

    function createDecoratorForService($delegate) {

        decorate($delegate, $delegate);

        return $delegate;
    }

    function createDecorator($delegate) {
        if (angular.isFunction($delegate)) {
            return createDecoratorForFunctionLikeService($delegate);
        }
        return createDecoratorForService($delegate);
    }

Bien que cette approche se soit avérée réalisable, comme le temps l'a montré, cette approche a ses propres inconvénients. Le principal est qu'il est impossible de déterminer quelle zone sera (racine ou angulaire) pour une partie spécifique de l'application hybride. Pour un fonctionnement normal, Angular2+ nécessite qu'il soit toujours dans la zone angulaire, et les décorateurs peuvent faire en sorte que les composants Angular2+ soient dans la zone racine. ngUpgrade implique que la zone angulaire couvre la majeure partie de l'application.

Pour éviter cela, il y a eu une deuxième tentative de correction avec une analyse plus approfondie des raisons, en abandonnant les décorateurs et en revenant au problème d'origine.

Maintenant, nous devons comprendre quel code rend la zone instable. Pour cela, zone.js dispose d'utilitaires spéciaux, tels que _TaskTrackingZoneSpec_ (_\node_modules\zone.js\dist\task-tracking.js_), qui permet de suivre quelles tâches se trouvent dans la zone et où elles ont été créées ( creationLocation ). De plus, au début de la partie Angular2+ de l'application, enregistrez la zone angulaire (window as any).a9Zone = (upgradeModule as any).ngZone._inner; et ajoutez ce code quelque part

$rootScope.$watch(function () {
    if (window.a9Zone) {
        console.log("Angular2+ task count:", window.a9Zone._zoneDelegate._taskCounts);

        // Optional.
        window.getAllAngularTestabilities().forEach(_ => {
            _.taskTrackingZone.macroTasks.forEach(t => {
                console.log(t, t.creationLocation);
            });
        });
    }
});

Ainsi, trouvez les endroits dans l'application où les microtâches et macrotâches sont dans la zone depuis longtemps (_microTasks.lenght! == 0 && macroTasks.lenght! == 0_).

Ensuite, vous devez sortir ce code de la zone angulaire. Pour ce faire, enveloppez le code dans ngZone.runOutsideAngular . Cependant, faites-le très soigneusement et assurez-vous que la zone racine n'apparaît pas là où elle n'est pas nécessaire.

Une fois que la zone angulaire est devenue stable, le rapporteur ne tombera plus avec des délais d'

J'espère que cela t'aides.

J'ai proposé un hack temporaire pour résoudre ce problème en remplaçant la fonction waitForAngular avec la logique ci-dessous.

onPrepare: function() {

            var script = "var callback = arguments[arguments.length - 1];" +
                "angular.element(document.querySelector(\"body\")).injector()"+
                ".get(\"$browser\").notifyWhenNoOutstandingRequests(callback)");

            browser.waitForAngular = function() {
                return browser.executeAsyncScript(script)
            };
}
Cette page vous a été utile?
0 / 5 - 0 notes