Методы действий (async/await)

Урок: Методы действий (async/await)

Введение

На прошлом уроке мы научились описывать страницы как классы (Page Object) и выносить действия пользователя в методы.

Но если ты внимательно посмотришь на код Playwright, то заметишь одну важную деталь:

const page = {
  click: async (selector) => console.log('click', selector),
};

(async () => {
  await page.click('#submit');
})();

Почему здесь есть await? Почему нельзя просто вызвать page.click() и сразу идти дальше?

Дело в том, что взаимодействие с браузером — это не мгновенное действие:

  • элемент может появиться не сразу;
  • страница может загружаться;
  • запросы могут выполняться с задержкой.

Поэтому в Playwright почти все операции возвращают Promise, и их нужно дождаться через await (или .then).


Что значит «асинхронность» простыми словами

Если бы код не ждал завершения действия — он пошёл бы дальше, а UI ещё не обновился. В тестах это ломает проверки.


Что такое async и await

async

Ключевое слово async делает функцию асинхронной (она возвращает Promise).

async function example() {
  console.log('example');
}

example().then(() => console.log('готово'));

В классе метод тоже может быть async:

class Demo {
  async fillLogin(login) {
    console.log('fill', login);
  }
}

(async () => {
  const d = new Demo();
  await d.fillLogin('user');
})();

await

await говорит: «подожди, пока Promise справа завершится».

const page = {
  click: async (selector) => console.log('click', selector),
};

(async () => {
  await page.click('#submit');
})();

Почему это важно в Playwright

Все основные методы Playwright — асинхронные (goto, fill, click…).

Любой метод, который их вызывает, обычно тоже объявляют async и внутри используют await.


Обновляем наш Page Object

Старый вариант без await внутри (возвращает Promise наружу):

const page = {
  fill: async (s, v) => console.log('fill', s, v),
  click: async (s) => console.log('click', s),
};

class LoginPage {
  constructor(page) {
    this.page = page;
    this.loginInput = '#login';
    this.passwordInput = '#password';
    this.submitButton = '#submit';
  }

  fillLogin(login) {
    return this.page.fill(this.loginInput, login);
  }
}

(async () => {
  const lp = new LoginPage(page);
  await lp.fillLogin('user');
})();

Перепишем «как принято» — async-методы и await внутри:

const page = {
  fill: async (s, v) => console.log('fill', s, v),
  click: async (s) => console.log('click', s),
};

class LoginPage {
  constructor(page) {
    this.page = page;
    this.loginInput = '#login';
    this.passwordInput = '#password';
    this.submitButton = '#submit';
  }

  async fillLogin(login) {
    await this.page.fill(this.loginInput, login);
  }

  async fillPassword(password) {
    await this.page.fill(this.passwordInput, password);
  }

  async clickSubmit() {
    await this.page.click(this.submitButton);
  }
}

(async () => {
  const loginPage = new LoginPage(page);
  await loginPage.fillLogin('user');
  await loginPage.fillPassword('123');
  await loginPage.clickSubmit();
})();

Что изменилось:

  • добавили async к методам;
  • внутри используем await;
  • методы гарантируют, что шаг завершился, прежде чем вернуть управление.

Что будет, если забыть await

Вызов async-функции без await даёт Promise, а не результат:

const page = {
  click: async (s) => console.log('click', s),
};

class LoginPage {
  constructor(page) {
    this.page = page;
    this.submitButton = '#submit';
  }

  async clickSubmit() {
    await this.page.click(this.submitButton);
  }
}

(async () => {
  const loginPage = new LoginPage(page);
  const p = loginPage.clickSubmit();
  console.log('это Promise?', p instanceof Promise);
  await p;
})();

Правильно дождаться:

const page = {
  click: async (s) => console.log('click', s),
};

class LoginPage {
  constructor(page) {
    this.page = page;
    this.submitButton = '#submit';
  }

  async clickSubmit() {
    await this.page.click(this.submitButton);
  }
}

(async () => {
  const loginPage = new LoginPage(page);
  await loginPage.clickSubmit();
})();

Асинхронный метод более высокого уровня

const page = {
  fill: async (s, v) => console.log('fill', s, v),
  click: async (s) => console.log('click', s),
};

class LoginPage {
  constructor(page) {
    this.page = page;
    this.loginInput = '#login';
    this.passwordInput = '#password';
    this.submitButton = '#submit';
  }

  async login(login, password) {
    await this.page.fill(this.loginInput, login);
    await this.page.fill(this.passwordInput, password);
    await this.page.click(this.submitButton);
  }
}

(async () => {
  const loginPage = new LoginPage(page);
  await loginPage.login('user', '123');
})();

Как это связано с this

Методы по-прежнему используют this.page и поля селекторов:

const page = {
  click: async (s) => console.log('click', s),
};

class LoginPage {
  constructor(page) {
    this.page = page;
    this.submitButton = '#submit';
  }

  async clickSubmit() {
    await this.page.click(this.submitButton);
  }
}

(async () => {
  const lp = new LoginPage(page);
  await lp.clickSubmit();
})();

Типичная структура Page Object с async/await

const page = {
  fill: async (s, v) => console.log('fill', s, v),
  click: async (s) => console.log('click', s),
};

class LoginPage {
  constructor(page) {
    this.page = page;
    this.loginInput = '#login';
    this.passwordInput = '#password';
    this.submitButton = '#submit';
  }

  async fillLogin(login) {
    await this.page.fill(this.loginInput, login);
  }

  async fillPassword(password) {
    await this.page.fill(this.passwordInput, password);
  }

  async clickSubmit() {
    await this.page.click(this.submitButton);
  }

  async login(login, password) {
    await this.fillLogin(login);
    await this.fillPassword(password);
    await this.clickSubmit();
  }
}

(async () => {
  const loginPage = new LoginPage(page);
  await loginPage.login('user', '123');
})();

Связь с предыдущими уроками

  • Урок 1: класс — структура
  • Урок 2: методы — поведение
  • Урок 3: класс = страница (Page Object)
  • Урок 4 (сейчас): методы = асинхронные действия с await

Итоговое понимание

Асинхронность — основа работы с браузером.

Все действия в Playwright происходят не мгновенно, поэтому методы класса должны быть асинхронными и использовать await.

async/await позволяет:

  • выполнять действия последовательно;
  • дожидаться результата;
  • писать стабильные тесты.

В следующем уроке мы соберём всё вместе и начнём писать полноценные тесты с использованием этих классов.