Потому что ни одно одностраничное приложение не существует в вакууме.

Одностраничные приложения не существуют в вакууме. В таких секторах, как банковское дело, финансы или страхование, мы обычно видим, что наши приложения скрыты за шлюзами единого входа или являются частью более крупного рабочего процесса, часто включающего сочетание устаревших систем и других одностраничных приложений.

Выполнение автоматизированного теста с перемещением по этим различным системам раньше было проблемой, но теперь это не так. Благодаря Serenity / JS мы можем даже сделать его довольно элегантным.

Эта статья покажет вам, как взаимодействовать как с приложениями Angular, так и с приложениями, не относящимися к Angular, в рамках одного и того же сценария тестирования.

Угловая синхронизация

Как вы, возможно, уже знаете, одна из специальных функций, которые предлагает Protractor, - это автоматическое ожидание. Эта функция информирует тест об Angular и означает, что Protractor будет ждать, пока приложение Angular будет готово, перед выполнением теста. Он также будет ждать завершения всех запросов XHR и тайм-аутов, прежде чем перейти к следующему этапу сценария.

Все это очень полезно, когда нам нужно взаимодействовать только с одним приложением Angular, но вызывает проблемы на веб-сайтах, отличных от Angular, поскольку Protractor пытается дождаться вещей, которые никогда не произойдут, и в конечном итоге отказывается:

Error: Timed out waiting for Protractor to synchronize with the page after 11 seconds

Отключение синхронизации с Serenity / JS

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

Serenity / JS (≥ 1.3.0) поставляется с выделенным взаимодействием, которое делает именно это и обеспечивает правильное связывание этого действия с другими задачами, которые выполняют актеры:

actor.attemptsTo(
  UseAngular.disableSynchronisation(),
  // ... interact with a non-Angular app
  UseAngular.enableSynchronisation(),
  // ... interact with an Angular app
)

Практический пример

Так как же использовать это на практике? Давайте представим сценарий, в котором мы хотим, чтобы наш актер ввел в Google демонстрацию Angular / Protractor Джули - Супер калькулятор, выбрал его из списка результатов поиска и использовал приложение для выполнения некоторой арифметической операции.

Поскольку google.co.uk не является веб-сайтом Angular, нам необходимо отключить функцию автоматической синхронизации Protractor и указать актеру дождаться появления результатов поиска.

Выбрав интересующий результат поиска, мы перенаправляемся в приложение Angular. Это означает, что мы можем снова включить функцию синхронизации и позволить Protractor обрабатывать асинхронный расчет результата.

Сценарий, выраженный в Mocha, мог выглядеть так:

describe('Cross-application testing', () => {
  
  describe('Combining Protractor and Serenity/JS', () => {
    
    const Steph = Actor.named('Steph').
                  whoCan(BrowseTheWeb.using(protractor.browser));

    it('lets you switch between the apps', () => Steph.attemptsTo(
      Google.the('juliemr calculator'),
      SelectResult.of('Super Calculator'),
      Multiply.number(6).by(7),
      Ensure.result(equals('42')),
    ));
  });
});

Поскольку задача «в Google» включает взаимодействие с приложением, отличным от Angular, in вызывает UseAngular.disableSynchronisation() перед переходом на соответствующий веб-сайт:

export class Google implements Task {
  static the = (term: string) => new Google(term);

  performAs = (actor: PerformsTasks) => actor.attemptsTo(
      UseAngular.disableSynchronisation(),
      Open.browserOn('http://google.co.uk/'),
      Enter.theValue(this.term).into(GoogleSearch.Query).
            thenHit(protractor.Key.ENTER),
  );

  constructor(private term: string) {
  }
}

Если синхронизация отключена, мы должны указать актеру дождаться результатов:

export class SelectResult implements Task {
  static of = (result: string) => new SelectResult(result);

  performAs = (actor: PerformsTasks) => actor.attemptsTo(
    Wait.until(GoogleSearch.Result.of(this.result), Is.clickable()),
    Click.on(GoogleSearch.Result.of(this.result)),
  )

  constructor(private result: string) {
  }
}

Но как только мы выбрали результат поиска, мы можем снова включить автоматическую синхронизацию:

export class Multiply implements Task {
 static number = (multiplier: number) => ({
   by: (multiplicand: number) => 
     new Multiply(multiplier, multiplicand),
   })

 performAs(actor: PerformsTasks) => actor.attemptsTo(
   UseAngular.enableSynchronisation(),
   Enter.theValue(this.multiplier).into(Calculator.Left_Operand),
   Select.theValue('*').from(Calculator.Operator),          
   Enter.theValue(this.multiplicand).into(Calculator.Right_Operand),
   Click.on(Calculator.Go),
 )

 constructor(private multiplier: number, 
             private multiplicand: number) { }
}

Покажи мне код

Вы можете найти рабочий проект, демонстрирующий вышеуказанную функциональность, на github по адресу serenity-js / tutorial-cross-app-testing, а узнать больше о Serenity / JS на serenity-js.org

Я обучаю команды и отдельных лиц по всему миру BDD, разработке программного обеспечения и автоматизации тестирования, так что свяжитесь с нами, если вы хотите узнать, как Serenity / JS может помочь вашей команде!

Понравилось чтение? Пожалуйста, нажмите ниже, чтобы другие люди увидели эту статью здесь, на Medium. Возможно, вам понравятся мои другие статьи и руководства!