Запустите ngrx / effect за пределами зоны Angular, чтобы предотвратить тайм-аут в Protractor

Я только начал писать тесты e2e для своего приложения, и у меня возникли проблемы с тайм-аутом с Protractor и ngrx / effects.

У меня есть следующий эффект отправки действия каждые пару минут:

@Effect() setSessionTimer$ = this.actions$
        .ofType(Auth.ActionTypes.SET_SECONDS_LEFT)
        .map(toPayload)
        .switchMap(secondsLeft => Observable.concat(
            Observable.timer((secondsLeft - 60) * 1000).map(_ => new Auth.SessionExpiringAction(60)),
            Observable.timer(60 * 1000).map(_ => new Auth.SessionExpiredAction())
        ));

Попытка запустить тест Protractor приводит к тайм-ауту теста со следующей ошибкой, поскольку Angular нестабилен.

Ошибка: время ожидания завершения асинхронных задач Angular истекло через 11 секунд. Это может быть связано с тем, что текущая страница не является приложением Angular. Дополнительные сведения см. В разделе часто задаваемых вопросов: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular В ожидании элемента с локатором - Локатор: По (селектор css, .toolbar-title)

Согласно этой проблеме (https://github.com/angular/protractor/issues/3349) Мне нужно использовать NgZone для запуска интервала Observable вне Angular. Я пробовал разные комбинации this.ngZone.runOutsideAngular(), но ничего не помогло, и тесты продолжаются.

Например, это не работает:

@Effect() setSessionTimer$ = this.actions$
        .ofType(Auth.ActionTypes.SET_SECONDS_LEFT)
        .map(toPayload)
        .switchMap(secondsLeft => this.ngZone.runOutsideAngular(() => Observable.concat(
            Observable.timer((secondsLeft - 60) * 1000).map(_ => new Auth.SessionExpiringAction(60)),
            Observable.timer(60 * 1000).map(_ => new Auth.SessionExpiredAction())
        )));

Я понятия не имею, как запустить эффект вне Angular. Кто-нибудь успешно тестировал свое приложение ngrx с помощью e2e?


person Alexander Ciesielski    schedule 30.03.2017    source источник


Ответы (2)


Решение состоит в том, чтобы запланировать запуск наблюдаемого таймера за пределами NgZone, а затем повторно войти в зону, когда произойдет что-то интересное.

Сначала вам понадобятся две служебные функции, которые обертывают любой планировщик и заставляют эффект входить в зону или выходить из нее:

import { Subscription } from 'rxjs/Subscription';
import { Scheduler } from 'rxjs/Scheduler';
import { NgZone } from '@angular/core';


class LeaveZoneSchduler {
  constructor(private zone: NgZone, private scheduler: Scheduler) { }

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

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

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

export function leaveZone(zone: NgZone, scheduler: Scheduler): Scheduler {
  return new LeaveZoneSchduler(zone, scheduler) as any;
}

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

Затем, используя планировщик (например, asap или async), вы можете заставить поток входить в зону или выходить из нее:

import { async } from 'rxjs/scheduler/async';
import { enterZone, leaveZone } from './util';

actions$.ofType('[Light] Turn On')
    .bufferTime(300, leaveZone(this.ngZone, async))
    .filter(messages => messages.length > 0)
    .observeOn(enterZone(this.ngZone, async))

Обратите внимание, что большинство операторов, зависящих от времени (например, bufferTime, debounceTime, Observable.timer и т. Д.), Уже принимают альтернативный планировщик. Вам нужно только observeOn, чтобы повторно войти в зону, когда произойдет что-то интересное.

person Mike R    schedule 03.04.2017
comment
Оно работает! Как вы узнали это о планировщиках и наблюдателе? - person Alexander Ciesielski; 04.04.2017
comment
Отлично работает! Спасибо! - person George Oiko; 03.05.2017
comment
Это сработало отлично! Планировщик устарел, теперь он SchedulerLike. - person Colton Williams; 14.09.2020
comment
Спасибо, Майк! Действительно, некоторые API устарели, просто используйте образец кода из следующего ответа @mohlendo. - person mitschmidt; 11.02.2021
comment
В примере использования лучше использовать queue (import { queue } from 'rxjs/scheduler/async';) при выходе из зоны - .observeOn(enterZone(this.ngZone, queue)). Если используется async, то обнаружение изменений запускается дважды. - person sax; 23.04.2021

Для Angular 6 и RxJS 6 используйте следующий код:

import { SchedulerLike, Subscription } from 'rxjs'
import { NgZone } from '@angular/core'

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

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 leaveZone(zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
  return new LeaveZoneScheduler(zone, scheduler)
}

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

Эффект должен выглядеть так:

import { asyncScheduler, queueScheduler } from 'rxjs'
import { filter, observeOn, bufferTime } from 'rxjs/operators'
import { enterZone, leaveZone } from './util';

actions$.ofType('[Light] Turn On')
  .pipe(
    bufferTime(300, leaveZone(this.ngZone, asyncScheduler)),
    filter(messages => messages.length > 0),
    observeOn(enterZone(this.ngZone, queueScheduler)),
  )
person mohlendo    schedule 01.06.2018
comment
Отлично работает - спасибо, что поделились. - person mitschmidt; 11.02.2021