运行 ngrx/effect 在 Angular 的区域之外以防止 Protractor 超时

Run ngrx/effect outside of Angular's zone to prevent timeout in Protractor

我刚开始为我的应用程序编写端到端测试,并且 运行正在解决 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())
        ));

尝试 运行 量角器测试会导致测试超时并出现以下错误,因为 Angular 不稳定。

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, .toolbar-title)

根据这个问题 (https://github.com/angular/protractor/issues/3349) 我需要使用 NgZone 来 运行 Angular 之外的一个间隔 Observable。我尝试了 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 之外的效果。 有人成功地通过 e2e 测试了他们的 ngrx 应用程序吗?

解决方案是将 timer observable 调度到 运行 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;
}

然后使用调度程序(如 asapasync)您可以使流进入或离开区域:

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

请注意,大多数基于时间的运算符(如 bufferTimedebounceTimeObservable.timer 等)已经接受了替代调度程序。当发生有趣的事情时,您只需要 observeOn 重新进入该区域。

对于 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)),
  )