Protractor: O transferidor falha quando o aplicativo é inicializado com o ngUpgrade

Criado em 16 mai. 2017  ·  51Comentários  ·  Fonte: angular/protractor

Estou trabalhando na migração de um aplicativo AngularJS (1.6) para Angular (4) e agora tenho um aplicativo híbrido, inicializado com NgUpgrade. Isso parece ter quebrado completamente meus testes de transferidor.

Perdoe-me se isso é apenas algo que estou fazendo de errado, mas não consigo encontrar uma resposta para o motivo pelo qual isso não funcionará.

O erro que recebo é:

Falha: Tempo limite esgotado ao aguardar a conclusão de tarefas angulares assíncronas após 11 segundos. Isso pode ocorrer porque a página atual não é um aplicativo Angular. Consulte as Perguntas frequentes para obter mais detalhes: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting -for-angular

Enquanto espera pelo elemento com localizador - Localizador: Por (seletor css, botão .toggle-summary)

Mudanças de aplicativo híbrido

O aplicativo parece estar funcionando bem e os componentes AngularJS e Angular estão funcionando conforme o esperado. As alterações que fiz no processo de bootstrapping são:

1 ng-app removido da tag html:

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

2 Adicionado um AppModules (@NgModule) etc.

3 Usei o NgUpgrade para inicializar o aplicativo:

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

Testes de transferidor

Com base no erro acima, o problema parece estar relacionado a tudo o que o Transferidor faz quando está aguardando o Angular. Eu tenho um bloco beforeEach que carrega a página de login, preenche os detalhes e faz o login. Estranhamente, ele ainda está abrindo a página e inserindo texto no campo de nome de usuário, mas não segue adiante.

Eu tentei, sem sucesso:

  • adicionando "rootElement: 'body'" ao meu arquivo de configuração do transferidor
  • adicionando "ng12Hybrid: true" ao meu arquivo de configuração do transferidor - recebo uma mensagem dizendo que ele não deve mais ser necessário, pois ele detecta automaticamente.
  • aumentando a configuração allScriptsTimeout de 11.000 para 60.000 e ainda assim o tempo limite.
  • desativando a configuração waitForAngularEnabled. Isso resolve o problema com os campos de login, mas nenhuma das minhas simulações de http funciona e os testes falham.

Comentários muito úteis

Olá, estamos trabalhando em uma mudança para permitir que você configure quais tarefas o Transferidor espera, que pode ajudá-lo a lidar com o problema acima. Esta é uma ampla mudança envolvendo Zone.js e Angular e não tem ETA específico (será da ordem de semanas). Irá atualizar o progresso aqui.

Todos 51 comentários

Tive um problema semelhante que foi resolvido envolvendo este código de atualização assim:

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

E na configuração NÃO ESTOU USANDO nenhum desses:

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

Você poderia dar uma olhada em https://github.com/angular/protractor/issues/4234 e ver se você está tendo o mesmo problema? Além disso, no ngUpgrade $interval agora bloqueará o Protractor onde antes (https://github.com/angular/angular/issues/16349), mas estou trabalhando nisso.

@Evilweed - obrigado pela sugestão, infelizmente isso não parece resolver para mim.

@heathkit - encontrei um intervalo $ longo em execução pesquisando todo o projeto, mas mesmo depois de removê-lo, o problema persiste. Eu também comentei todos os usos de $ interval ou $ timeout em todo o aplicativo e ainda falha com o mesmo erro. Você sabe se há alguma maneira de depurar e descobrir o que pode estar bloqueando se for um problema com algo assíncrono?

Você pode postar seu erro completo? Ele deveria mostrar quais tarefas estão pendentes na mensagem de erro, mas é possível que algumas delas estejam faltando. Por exemplo, o erro ainda não mostrará tarefas pendentes do lado angular. Se você tem tarefas assíncronas de longa execução na parte Angular de seu aplicativo, precisará executá-las fora da zona Angular para evitar o bloqueio do Transferidor. (Consulte https://github.com/angular/protractor/blob/master/docs/timeouts.md)

Obrigado novamente por responder. O detalhe do erro está abaixo. Algumas coisas a serem observadas:

  • Meu arquivo de teste tem um bloco beforeAll que deve preencher os campos de nome de usuário e senha para fazer o login. Ele preenche o campo de nome de usuário com êxito, mas atinge o tempo limite em waitForAngular e não preenche o campo de senha. É disso que se trata a primeira metade do erro. A segunda metade é o teste de falha real (o que faz sentido, porque ele falhou ao fazer o login e o elemento não está presente).

  • O erro sugere que o problema pode resultar de uma chamada HTTP, portanto, talvez não seja $ interval ou $ timeout.

  • Não tenho muito do lado Angular no momento, quase tudo ainda é AngularJS. Não chega a mostrar o novo componente Angular porque não consegue fazer o login (então eu duvido que a chamada http tenha a ver com o componente 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)

Exatamente o mesmo - com uma nova configuração angular4 de 'início rápido' e um teste de transferidor muito básico. Tudo está funcionando bem no modo não híbrido.

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)

Ainda estou vendo esse problema.
Usando: @ angular / upgrade 4.2.2
que contém a correção de @heathkit (https://github.com/angular/angular/pull/16836) para tornar a API de

Se alguém pudesse compartilhar um repositório que mostre o problema, analisarei melhor.

O que descobri até agora

Depois de algumas pesquisas, descobri uma das causas do tempo limite que estou observando (ou uma delas, acho que não é a única). Acontece que uma das dependências que estou usando está chamando window.setTimeout() que está impedindo o Protractor de sincronizar (no meu caso, é
momento angular .

Isso funcionou bem quando meu aplicativo estava executando apenas AngularJS, mas causa um problema de tempo limite quando inicializado como um aplicativo híbrido (provavelmente devido à maneira como ele é executado nas zonas).

Repo de exemplo

Criei um repositório de exemplo em https://github.com/mattwilson1024/angular-hybrid . É um aplicativo híbrido (NgUpgrade) com as seguintes partes:

  • A página mais externa (a CharactersPage) é fornecida por AngularJS (1.6).
  • A página contém CharacterListComponent, que é um componente Angular (4) rebaixado.
  • O CharacterListComponent usa outro componente Angular 4, o componente CharacterDetail.

O branch master contém o aplicativo de trabalho e um único transferidor testa que não passa nenhum problema.

O branch de momento angular adiciona uma dependência da biblioteca angular-moment e usa sua diretiva am-time-ago para adicionar um rótulo à página mostrando quantos anos atrás ela se passou desde o primeiro episódio. O aplicativo em si funciona bem, mas o teste do transferidor agora falha ao sincronizar.

Timeouts

Para confirmar que isso está acontecendo, tentei adicionar vários tipos diferentes de tempo limite ao controlador AngularJS, com os seguintes resultados. Isso confirmou que $ interval funciona, mas $ timeout, setInterval ou setTimeout não.

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

Próximos passos

Tendo conseguido rastrear o que está acontecendo, ainda não tenho certeza de qual é a solução. Eu entendo que pode ser necessário executar certas partes do código "fora da zona Angular", embora não tenha certeza de como fazer isso a partir de segmentos AngularJS onde não tenho uma classe TypeScript para injetar a zona etc.

Mesmo assim, se o código do problema está acontecendo dentro de uma biblioteca de terceiros (como é o caso no meu exemplo com momento angular) - qual seria a abordagem para contornar este problema para fazer os testes funcionarem como funcionavam antes de atualizar para um configuração híbrida?

As zonas são definidas na janela, então você não precisa injetar NgZone. Você pode correr fora da zona angular no AngularJS assim

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

Você está correto ao afirmar que qualquer uso de setTimeout ou setInterval, mesmo em código de terceiros, pode bloquear a estabilidade do Transferidor e causar tempos limite. Estamos adicionando rastreamento de tarefa ao Angular e AngularJS que deve facilitar a depuração desses problemas, mostrando a você de onde veio a tarefa assíncrona em que o Protractor expirou. Realisticamente, levará alguns meses até que possamos colocar isso no Transferidor.

No que diz respeito ao momento angular, sua melhor opção pode ser enviar um PR para eles tornando-o ciente da zona, conforme mostrado acima. Caso contrário, você pode desligar waitForAngular seletivamente e usar browser.wait e as condições esperadas. Estamos trabalhando em uma API waitForAngular mais flexível que permitirá que você controle quais tarefas espera, mas isso ainda está um pouco distante.

Em nossa situação, os testes podem ser escritos da maneira genérica (sem sincronização angular) ( browser.ignoreSynchronization = true usando ECs, etc.), mas falham ao definir browser.ignoreSynchronization = false; com "Error while waiting for Protractor to sync with the page: "Cannot read property '$$testability' of undefined"

Isso basicamente anula todo o nosso

Usando o Transferidor 5.1.2, se eu definir ng12Hybrid: true , recebo a mensagem:

Você definiu ng12Hybrid. A partir do Protractor 4.1.0, o Protractor pode inferir automaticamente se você está usando um aplicativo ngUpgrade (contanto que ng1 seja carregado antes de chamar platformBrowserDynamic ()), e este sinalizador não é mais necessário para a maioria dos usuários

Obviamente, a documentação do angular.io está desatualizada aqui: https://angular.io/guide/upgrade#e2e -tests

Últimas palavras famosas do mesmo documento:

Os testes E2E não estão realmente preocupados com a estrutura interna dos componentes do aplicativo. Isso também significa que, embora você modifique um pouco o projeto durante a atualização, o conjunto de testes E2E deve continuar passando com apenas pequenas modificações. Você não alterou o comportamento do aplicativo do ponto de vista do usuário.

À luz dessa declaração no angular.io:

@heathkit Obrigado pela dica sobre a execução dentro e fora das zonas, mas para ser honesto, estamos procurando uma solução que seja executada com nosso código AngularJS existente, como está. Temos um amplo pacote de Protractor que se baseia na sincronização implícita do AngularJS. Nosso objetivo é migrar nosso AngularJS para Angular usando o ngUpgrade, mas esse problema de não poder executar nosso pacote Protractor existente no modo de atualização Híbrida frustrou nossa iniciativa. Uma solução sensata seria muito bem-vinda agora.

Se estivermos fazendo algo fundamentalmente errado, certamente gostaríamos de saber o quê.

Enfrentando o mesmo problema! : 0

Vendo a mesma coisa aqui: AngularJS 1.6 -> Angular 4.2.4

Alguém conhece algum tipo de solução alternativa, por maior que seja? Sem voltar para 1.6, claro.

@ quinw68 - @heathkit sugere um método para executar o código fora da 'Zona' Angular em sua resposta anterior neste tópico. Acho que isso significa mudanças no código-fonte do seu aplicativo para fazê-lo funcionar, e pode até depender de bibliotecas de terceiros adicionando suporte. Por favor me corrija se eu estiver errado.

Outra 'solução' que conheço é tratar seu aplicativo existente como se ele não fosse mais um aplicativo AngularJS. Isso significa que você terá que alterar seus testes de transferidor para que eles não esperem mais inerentemente pelo Angular, mas verifiquem explicitamente a presença de elementos na página antes de consultá-los quanto ao seu estado. Dependendo do tamanho do seu aplicativo, isso pode ser uma tarefa assustadora.

Espero que ajude.

Até onde eu entendo o problema, isso não é específico do ngUpgrade, mas sim específico do Angular. Quero dizer, mesmo em um aplicativo Angular puro (2+), ter um setTimeout longo (diretamente ou por meio de uma biblioteca de terceiros) fará com que o transferidor expire.

@heathkit , você pode confirmar isso ou eu perdi alguma coisa? O ngUpgrade está prejudicando o Protractor ou piorando as coisas de alguma forma?

@gkalpak Você está certo ao dizer que este é mais um problema específico do Angular, mas as pessoas o encontram quando usam o ngUpgrade com um aplicativo AngularJS grande.

No AngularJS, o Protractor esperaria apenas por tarefas assíncronas iniciadas com $browser.defer() - basicamente apenas $timeout e $http chamadas no código do aplicativo. Assim que alguém começar a usar o ngUpgrade, seus testes de Protractor existentes começarão a expirar devido a longas chamadas setTimeout (potencialmente de bibliotecas de terceiros), onde antes não ocorriam. Então, principalmente, o problema é que o ngUpgrade está funcionando conforme o esperado, e agora os testes do aplicativo AngularJS e do Protractor das pessoas estão repentinamente sujeitos à semântica Angular (2+).

Então, como alguém resolveria isso para um aplicativo Angular puro? O que alguém faria se uma biblioteca Angular de terceiros estivesse configurando um setTimeout longo, causando o tempo limite do Transferidor?

@vikerman Ouvi dizer que você vai ajudar a colocar um novo membro da equipe em funcionamento em breve, então designando para você até que eles estejam a bordo.

Na verdade, não temos uma boa resposta para o caso em que um aplicativo de terceiros está definindo um longo tempo limite. No momento, tudo que você pode fazer é desligar waitForAngular e confiar em ExpectedConditions, mas isso não é o ideal. Estou trabalhando para adicionar um recurso ao Protractor que dará às pessoas mais controle sobre quais macrotarefas seus testes esperam, mas isso ainda está um pouco distante.

@heathkit @vikerman Alguma atualização sobre este, por favor?

O projeto em que trabalho tinha esse problema antes, mas eles envolveram a função de pesquisa contínua para ser executada fora da zona angular de forma que não afetasse browser.waitForAngular ();

Em primeiro lugar, obrigado à equipe / contribuidores do Transferidor . Protractor tem sido a base para meu aplicativo ter poucos problemas de produção ao longo de dezenas de lançamentos em 3 anos, mais de 200 serviços, diretivas, componentes, controladores com cerca de 400 testes do Protractor e2e.

Este é um bom lembrete do quanto eu confio no Protractor, mas esse problema interrompeu minha atualização Angular após ter dedicado 2 semanas comprometido a apenas inicializar o ngUpgrade e as necessidades auxiliares. Isso é feito, mas agora os testes e2e não podem ser executados e a implantação foi interrompida sem um caminho visível para a frente (ainda).

Graças às ótimas informações de @ mattwilson1024 , certifiquei-me de que $timeout , setTimeout , setInterval não sejam usados ​​em nosso aplicativo.

Mas descobri que angular-ui-bootstrap (v.2.5.6) e ui-select (v0.19.8) usam $timeout . Eu uso ambas as bibliotecas extensivamente. Parece que muitos outros confiam neles também - angular-ui-bootstrap - 400k npm downloads / mo, ui-select 100k + / mo.

angular-ui-bootstrap criou um problema em 2015 para substituir $ timeout por $ interval especificamente devido a esse problema, mas infelizmente decidiu "este bug deve ser corrigido no transferidor e não aqui. Fechando isto." Além disso, angular-ui-bootstrap não está mais fornecendo atualizações em liu de sua versão Angular @ ng-bootstrap / ng-bootstrap que está em beta.

Fico surpreso quando vejo bibliotecas angulares significativamente usadas que não garantem a validade do Protractor. Sobre isso, concordo com @heathkit que essas bibliotecas precisam se consertar. Mas, espero que o Transferidor possa intervir e mitigar tudo de uma só vez. Mesmo uma boa solução alternativa seria excelente. Estou desapontado por ter que voltar atrás no meu ngUpgrade por enquanto até que eu desenterre todas as dependências de $timeout e, possivelmente, ao invés de apenas incluir o ngGrade e começar a escolher itens para atualização quando possível, eu provavelmente tem uma dependência para atualizar ou migrar desses itens de terceiros junto com a adição de ngUpgrade. ECA.

Estou continuando a pesquisar para meu aplicativo (culpados específicos que causam o (s) tempo (s) limite (s), como seguir em frente, etc.) e relatarei qualquer coisa que possa ser útil para outras pessoas.

Onde encontrei $ timeout:
angular-ui-bootstrap v2.5.6

  • UibAlertController
  • UibCarouselController
  • UibDatepickerPopupController
  • UibTypeaheadController
  • UibTooltip

ui-select

  • uiSelect
  • uiSelectMultiple
  • uiSelectSingle
  • uiSelectSort
  • uisOpenClose

recurso angular $resource injeta e usa $timeout
filtro angular ( $window.setTimeout )

Olá, estamos trabalhando em uma mudança para permitir que você configure quais tarefas o Transferidor espera, que pode ajudá-lo a lidar com o problema acima. Esta é uma ampla mudança envolvendo Zone.js e Angular e não tem ETA específico (será da ordem de semanas). Irá atualizar o progresso aqui.

Eu tenho o mesmo problema quando eu inicializar meu [email protected] aplicativo com NgUpgrade para [email protected].
Obrigado pelos comentários acima, especialmente @ mattwilson1024 e @tonybranfort , que me ajudam a entender melhor o problema.

Eu encontrei um workaournd no meu caso, adicionando browser.ignoreSynchronization = true/false; ao redor do código que atinge o tempo limite.

Por exemplo, um de meus testes fez uma seleção simples em um grupo de botões de opção e esperava que a opção correta fosse selecionada:

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

Quando tento depurar esse código, descobri que o tempo limite ocorreu na última etapa. Suponho que o problema subjacente seja uma dica de ferramenta do angular-ui-bootstrap, com o uso de $ timeout: https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js

No entanto, não há chamadas assíncronas neste teste. Então, o que eu faço é simplesmente adicionar ignoreSynchronization no início e redefini-lo no final:

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

Não sei se essa é a forma recomendada ou se tornará os testes mais escamosos. Mas, para testes que não exigem nenhuma chamada assíncrona, mas ainda sofrem o problema de tempo limite, ele deve corrigi-los.

Olá @vikerman - Alguma atualização sobre isso? https://github.com/angular/protractor/issues/4290#issuecomment -337094393

@vikerman Existe alguma atualização sobre isso?

Alguém tem um bom conselho sobre como encontrar esses tempos limite de longa duração para que eu possa retirá-los do meu aplicativo?

De : Náufrago (s?) Na Ilha de AngularJS encalhado pelo Transferidor Edição # 4290
Para : @vikerman @juliemr

Ahoy! Podemos obter uma atualização sobre este problema? A última atualização em 16 de outubro foi um HEC na "ordem das semanas". Ou feche este problema para que possamos prosseguir.

Estas são as jangadas que estou considerando no momento:

  • Substitua tudo angular-ui-bootstrap / ui-select . Isso é significativo para mim. Isso precisa ser feito de qualquer maneira, mas minha preocupação é que algo mais apareça e eu ainda estarei preso no AngularJS depois desse trabalho. Lição aprendida: agrupar componentes de terceiros com meus próprios componentes.
  • waitForAngularEnabled(false) para todos os testes sugeridos por @vladotesanovic. Eu sou cético em relação a um aplicativo inteiro, mas não o testei. Eu uso waitForAngularEnabled em apenas uma situação agora e apenas para um caso muito específico.
  • Atualizar todo o aplicativo para Angular em vez de AngularJS / migração incremental lado a lado: Não, apenas não é uma opção com meus recursos.
  • Migre para Vue ou React em vez de Angular. No topo da lista, especialmente considerando que ambos podem coexistir com AngularJs.
  • Mova-se para o Titereiro e descarte todos os testes do Transferidor - Esta é provavelmente a direção estratégica.
  • OU uma correção de # 4290 e posso descomentar essas poucas linhas necessárias para começar a usar o ngUpgrade :)

Nossa equipe está em processo de atualização de nosso aplicativo complexo e os testes do transferidor estão falhando quando inicializados com NgUpgrade. Tivemos que reverter nossas alterações porque agora não podemos lidar com todos os testes com falha que são falsos positivos.

Onde estamos com esse problema? Estou preocupado em ter que mudar para uma estrutura de teste diferente para que possamos manter nossos planos de atualização do Angular.

@tonybranfort : Dependemos muito de ui-select e angular-ui-bootstrap - este é um grande obstáculo para nossa atualização. Parece que você assumiu a liderança nesse problema. Mantenha-nos atualizados! 😁

Nós temos o mesmo problema. Alguém do transferidor pode nos atualizar sobre isso, por favor?

Estamos no meio do caminho para atualizar nosso aplicativo do Angular V1 e odiamos ter esse motivo para voltar para o V1 e abandonar todo o trabalho feito para o upgrade para o Angular V5 até agora.

Jogando outra votação aqui, estamos no processo de atualização de nosso aplicativo para Angular e, no momento, estamos usando um aplicativo fragmentado de waitForAngularEnabled(false) para aprová-los sem colocar todo o pacote em maior risco. É extraordinariamente unideal, entretanto, uma solução melhor seria muito apreciada.

Acabamos de abordar esse problema também, especificamente no que diz respeito ao serviço $ interval. Às vezes, o transferidor está esperando por ele e às vezes não - ainda não encontrei o padrão.

Edição de longo tempo: aparentemente, depende de quando o intervalo é invocado (na zona ou não)

Olá @vikerman - Alguma notícia sobre as mudanças para permitir que você configure quais tarefas o Protractor espera?

O mesmo problema aqui.

@vikerman existe alguma atualização ou solução alternativa?

Estamos enfrentando os mesmos problemas. Seria ótimo se houvesse algum progresso nesta questão.

Infelizmente, eu tenho o mesmo problema. Há algum progresso à vista em relação a isso?

Estamos adicionando rastreamento de tarefa ao Angular e AngularJS que deve facilitar a depuração desses problemas, mostrando a você de onde veio a tarefa assíncrona em que o Protractor expirou.

@heathkit Isso já foi feito em algum lugar? estamos tendo dificuldade em identificar esses lugares em nossa base de código.

também seria bom ter alguma documentação que o transferidor está esperando. Este parece estar desatualizado: https://www.protractortest.org/#/system -setup. apenas menciona substituir $timeout por $interval . Nada sobre setTimeout ou setInterval.
alguns documentos estão aqui: http://www.protractortest.org/#/timeouts. mas não há dicas de como detectá-los ...

Eu acho que isso irá ajudá-lo a executar seus testes "híbrido + transferidor angular". Isso me ajudou. Verifique o link: https://github.com/ui-router/angular-hybrid/issues/39#issuecomment -309057814

Fizemos um pequeno progresso no entendimento do que está acontecendo com esse problema. Parece que estamos tendo o mesmo. Parece que o ciclo de observador / resumo do angularjs atualizado funciona de maneira um pouco diferente e pode permitir alguns loops de ciclo de resumo infinitos. Encontramos 1 com um terceiro que estávamos usando e estamos investigando o próximo. Mas basicamente o observador estava causando uma mudança no que estava sendo assistido, fazendo com que o observador fosse acionado, etc. Para testar se você está tendo o mesmo problema, no Chrome apenas abra as ferramentas de desenvolvimento, vá para desempenho, grave alguns segundos e veja se você tem chamadas NgZone sendo feitas muitas vezes. Se você fizer isso, então em algum lugar do seu código você terá um ciclo de resumo infinito. Analisar as chamadas pode ajudar a apontar você na direção do que está causando isso

Você também pode modificar um pouco o arquivo local zone.js e adicionar logs de console onde as tarefas de tempo limite / intervalo são corrigidas para ver o que as está chamando. Maneira fácil de fazer isso:

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

Editar: é tão divertido formatar código no celular!

@vikerman Você tem alguma atualização sobre isso? Seria ótimo se você pudesse dar sua opinião sobre quando isso será corrigido? Obrigado.

Você também pode modificar um pouco o arquivo local zone.js e adicionar logs de console onde as tarefas de tempo limite / intervalo são corrigidas para ver o que as está chamando. Maneira fácil de fazer isso:

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

Editar: é tão divertido formatar código no celular!

Onde você colocaria esse código para rastrear os tempos limites / intervalos?

@maurycyg patchTimer -> função resolver

Este tópico é desanimador de ver. Existe ALGUMA atualização sobre isso?

Uma possível solução alternativa para não usar $timeout é substituí-lo por $interval (para bibliotecas de terceiros)
O código abaixo resolveu o problema de tempo limite para mim (adição de verificação do navegador para garantir). No meu caso, o problema era causado por componentes de bootstrap angular, que infinitamente chamavam $ timeout com 0 atraso.

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

A partir do zone.js v0.8.9, você pode escolher quais módulos de API da web deseja corrigir para reduzir a sobrecarga introduzida pela correção desses módulos.
https://github.com/angular/zone.js/blob/master/MODULE.md

Adicione o arquivo zone-flags.ts com o conteúdo abaixo
(window as any).__Zone_disable_timers = true;

Importe-o em polyfills.ts antes de zone.js

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

Pareceu que a solução acima com a desativação dos temporizadores de zona confunde com Obserables, (ex. AsyncValidators). A solução que finalmente funcionou para mim foi o setTimeout de patching.

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

Talvez o problema não esteja no próprio transferidor, mas no código do aplicativo.

Com a transição do Angular JS, que usa um ciclo de resumo para rastrear mudanças, para um aplicativo híbrido com Angular2 + que usa zone.js para detectar mudanças, o Protractor começou a cair com o tempo limite.

O transferidor inicia a próxima ação de teste apenas quando o aplicativo estiver estável. Estabilidade no caso de um aplicativo híbrido significa estabilidade da parte Angular JS do aplicativo e do Angular2 + parte do aplicativo. Ambas as partes devem estar estáveis.

A estabilidade angular do JS é definida por meio de $$testability.whenStable . A estabilidade do Angular 2+ é determinada por window.getAngularTestability(...).whenStable . Você pode depurar o código do Protractor e ver por si mesmo. Para fazer isso, adicione a instrução debugger; ao arquivo waitForAngular _ \ node_modules \ protractor \ built \ clientsidescripts.js_;
Você notará que a estabilidade da zona Angular2 + nunca ocorre. Para estabilidade , todas as microtarefas e macrotarefas devem ser concluídas.

Agora entendemos o problema. A parte angular 2+ do aplicativo é sempre instável e, portanto, o transferidor espera indefinidamente até que se torne estável e caia com o tempo limite. A estabilidade angular JS no caso de um aplicativo híbrido não deveria ter mudado e não afeta os tempos limite do Transferidor. A partir disso, podemos concluir que é necessário consertar a instabilidade da zona Angular2 + e então o transferidor começará a funcionar conforme o esperado.

Primeira tentativa de corrigir:
Como havia pouco tempo e eu queria consertar tudo de uma vez, escrevi um código que tirou o agendamento de tarefas da zona 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);
    }

Embora essa abordagem tenha se mostrado viável, como o tempo mostrou, ela tem suas próprias desvantagens. A principal delas é que é impossível determinar qual zona será (raiz ou angular) para uma parte específica do aplicativo híbrido. Para operação normal, o Angular2 + exige que esteja sempre na zona Angular, e os decoradores podem fazer com que os componentes do Angular2 + estejam na zona raiz. ngUpgrade implica que a zona angular cobre a maior parte da aplicação.

Para evitar isso, houve uma 2ª tentativa de conserto com uma análise mais aprofundada dos motivos, abandonando os decoradores e voltando ao problema original.

Agora precisamos entender qual código torna a zona instável. Para isso, zone.js possui utilitários especiais, como _TaskTrackingZoneSpec_ (_ \ node_modules \ zone.js \ dist \ task-tracking.js_), que permite rastrear quais tarefas estão na zona e onde foram criadas ( creationLocation ). Além disso, no início da parte Angular2 + do aplicativo, salve a zona Angular (window as any).a9Zone = (upgradeModule as any).ngZone._inner; e adicione este código em algum lugar

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

Assim, encontre os locais no aplicativo onde as microtarefas e macrotarefas estão na zona por um longo tempo (_microTasks.lenght! == 0 && macroTasks.lenght! == 0_).

Em seguida, você precisa obter esse código da zona angular. Para fazer isso, envolva o código em ngZone.runOutsideAngular . No entanto, faça isso com muito cuidado e certifique-se de que a zona raiz não apareceu onde não é necessária.

Depois que a zona angular se torna estável, o transferidor não cairá mais com o tempo limite .

Espero que isto ajude.

Eu criei um hack temporário para resolver esse problema substituindo a função waitForAngular pela lógica abaixo.

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)
            };
}
Esta página foi útil?
0 / 5 - 0 avaliações