Зарегистрируйтесь, чтобы продолжить обучение

WebDrivers Тестирование фронтенда

Видео может быть заблокировано из-за расширений браузера. В статье вы найдете решение этой проблемы.

Web Drivers

Web Drivers - это инструменты для взаимодействия с браузером.

  • Интерфейс удаленного управления, который позволяет анализировать и управлять браузером
  • Платформонезависимый и не зависит от языка
  • Предоставляет набор интерфейсов для нахождения и управления элементами DOM
  • Не имеет прямого отношения к тестированию

Selenium

Selenium — один из популярных фреймворков для тестирования. Поддерживается всеми основными платформами и на всех браузерах. Он позволяет автоматизировать тестирование, имитировать действия пользователей.

Использование:

import { Builder, By, Key, until } from 'selenium-webdriver';

describe('web driver', () => {
  let driver;

  test('google first result', async () => {
    // Создаём инстанс вебдрайвера
    driver = new Builder().forBrowser('chrome').build();

    // Выполняем переход на страницу
    driver.get('https://www.google.com');

    // Получаем элемент ввода
    const input = driver.findElement(By.name('q'));

    // Вызываем на элементе события нажатия клавиш (ввод в поиск и Enter)
    await input.sendKeys('hello', Key.ENTER);

    // Дожидаемся пока не появится элемент и получаем его
    const firstResult = await driver.wait(until.elementLocated(By.css('h3')), 10000);

    // Выводим содержимое элемента
    console.log(await firstResult.getAttribute('textContent'));
  }, 10000);

  afterEach(() => {
    driver.quit();
  });
});

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

import { Builder, By, Key } from 'selenium-webdriver';

const URL = 'http://svelte3-todo.surge.sh/';

describe('web driver', () => {
  let driver;

  // Создаём драйвер перед выполнением тестов
  beforeAll(() => {
    driver = new Builder()
      .forBrowser('chrome')
      .build();
  });

  // Тест добавления таска
  test('add a task', async () => {
    // Выполняем переход на страницу
    driver.get(URL);

    // Возвращаем промис из теста
    return driver.findElement(By.className('js-todo-input')).sendKeys('Build App', Key.ENTER)
      // Асинхнронные запросы оборачиваем в цепочку промиса
      .then(() => driver.getPageSource())
      .then((source) => {
        expect(source.includes('Build App')).toBe(true);
      });
  }, 1000);

  // Тест отметки таска как пройденного
  test('mark a task complete', () => {
    driver.get(URL);

    return driver.findElement(By.className('js-todo-input')).sendKeys('Build App', Key.ENTER) // Создаём таск
      // Перед изменением таска, проверяем что он не завершен, для этого проверяем класс
      .then(() => driver.findElement(By.className('todo-item')).getAttribute('class')) // получаем класс, асинхронный запрос
      .then((className) => {
        // проверяем что класс не содержит 'done'
        expect(className.includes('done')).toBe(false);
        // Вызываем клик по задаче (отмечаем что она завершена)
        // это асинхронная операция, поэтому возвращаем промис
        return driver.findElement(By.css('label')).click();
      })
      // Снова получаем класс, асинхронный запрос, поэтому оборачиваем в цепочку then
      .then(() => driver.findElement(By.className('todo-item')).getAttribute('class'))
      .then((className) => {
        // Проверяем имя класса
        expect(className.includes('done')).toBe(true);
      });
  }, 1000);

  test('delete a task', () => {
    driver.get(URL);

    // Перед удалением также создаем таск
    return driver.findElement(By.className('js-todo-input')).sendKeys('Build App', Key.ENTER)
      // Кликаем по кнопке удаления
      .then(() => driver.findElement(By.className('delete-todo')).click())
      // Получаем содержимое страницы
      .then(() => driver.getPageSource())
      .then((source) => {
        // Проверяем что таск удалён
        expect(source.includes('Build App')).toBe(false);
      });
  }, 1000);

  afterAll(() => {
    driver.quit();
  });
});

Тоже самое с async await:

import { Builder, By, Key } from 'selenium-webdriver';

const URL = 'http://svelte3-todo.surge.sh/';

describe('web driver', () => {
  let driver;

  // Создаём драйвер перед выполнением тестов
  beforeAll(() => {
    driver = new Builder()
      .forBrowser('chrome')
      .build();
  });

  // Тест добавления таска
  test('add a task', async () => {
    // Выполняем переход на страницу
    driver.get(URL);

    // Создаём таск
    await driver.findElement(By.className('js-todo-input')).sendKeys('Build App', Key.ENTER);
    const source = await driver.getPageSource();
    expect(source.includes('Build App')).toBe(true);
  }, 1000);

  // Тест отметки таска как пройденного
  test('mark a task complete', async () => {
    driver.get(URL);

    // Создаём таск
    await driver.findElement(By.className('js-todo-input')).sendKeys('Build App', Key.ENTER);
    // Перед изменением таска, проверяем что он не завершен, для этого проверяем класс
    const classNameBefore = await driver.findElement(By.className('todo-item')).getAttribute('class'); // получаем класс

    // проверяем что класс не содержит 'done'
    expect(classNameBefore.includes('done')).toBe(false);
    // Вызываем клик по задаче (отмечаем что она завершена)
    await driver.findElement(By.css('label')).click();
    // Снова получаем класс
    const classNameAfter = await driver.findElement(By.className('todo-item')).getAttribute('class');

    // Проверяем имя класса
    expect(classNameAfter.includes('done')).toBe(true);
  }, 1000);

  test('delete a task', async () => {
    driver.get(URL);

    // Перед удалением также создаем таск
    await driver.findElement(By.className('js-todo-input')).sendKeys('Build App', Key.ENTER);
    // Кликаем по кнопке удаления
    await driver.findElement(By.className('delete-todo')).click();
    // Получаем содержимое страницы
    const source = await driver.getPageSource();
    // Проверяем что таск удалён
    expect(source.includes('Build App')).toBe(false);
  }, 1000);

  afterAll(() => {
    driver.quit();
  });
});

Cypress

Cypress — это e2e фреймворк для тестирования на JS, имеет свой тест-раннер, поддерживает множество языков.

Компонентное тестирование:

import * as React from 'react';
import { mount } from '@cypress/react';
import Button from './Button.jsx';

it('Button', () => {
  // Рендерим кнопку
  mount(<Button>Text button</Button>);
  // Кликаем по кнопке
  cy.get('button').contains('Test button').click();
});
// Тестируем завершения таска
it('complete todo', () => {
  // Отрываем страницу
  cy.visit('/');
  // Находим элемент ввода, имитируем ввод имени таска и нажатие Enter
  cy.get('.new-todo').type('write tests{enter}');
  // Отмечаем выполнение таска
  cy.contains('.todo-list li', 'write tests').find('.toggle').check();

  // Проверяем наличие класса
  cy.contains('.todo-list li', 'write tests').should('have.class', 'completed');

  // При установленном плагине cypress-plugin-snapshots можно создавать скриншоты
  cy.get('.todoapp').toMatchImageSnapshot({
    imageConfig: {
      threshold: 0.001,
    },
  });
});

// Тест добавления таска
it('adds todos', () => {
  // Открываем страницу
  cy.visit('/');

  // Создаём два таска
  cy.get('.new-todo')
    .type('write E2E tests{enter}')
    .type('add API tests as needed{enter}');

  // Проверяем что запросы были отправлены
  cy.request('/todos')
    .its('body')
    .should('have.length', 2)
    .and((items) => {
      // ...
    });
});

Playwright

Playwright — библиотека от Microsoft, так же поддерживает множество языков. Не имеет своего тестраннера.

Пример использования:

// Подключаем библиотеку
import playwright from 'playwright';

(async () => {
  // Тестируем на разных браузеров в цикле
  for (const browserType of ['chromium', 'firefox', 'webkit']) {
    // Запускаем браузер, получаем инстанс браузера
    const browser = await playwright[browserType].launch();
    // Получаем контекст браузера
    const context = await browser.newContext();
    // Получаем инстанс страницы
    const page = await context.newPage();
    // Открываем страницу
    await page.goto('https://mail.ru');
    // Вызываем событие
    await page.click('[data-testid="enter-password"]');
    // Создаем скриншот
    await page.screenshot({ path: `mail-${browserType}.png` });
    // Закрываем браузер
    await browser.close();
  }
})();

Имитация другого устройства:

import { webkit, devices } from 'playwright';

const iPhone11 = devices['iPhone 11 Pro'];

describe(() => {
  test('Main test', async () => {
    // Запускаем браузер, получаем инстанс браузера
    const browser = await webkit.launch();
    // Получаем контекст устройства, задаём свои настройки
    const context = await browser.newContext({
      ...iPhone11,
      locale: 'en-US',
      geolocation: { longitude: 12.492507, latitude: 41.889938 },
      permissions: ['geolocation'],
    });
    // Получаем инстанс страницы
    const page = await context.newPage();
    // Открываем страницу
    await page.goto('https://maps.google.com');
    // Вызываем событие
    await page.click('text="Your location"');
    // Ждём выполнение запроса
    await page.waitForRequest(/.*preview\/pwa/);
    // Создаём скриншот
    await page.screeshot({ path: 'iphone-11.png' });
    // Закрываем браузер
    await browser.close();
  });
});

Puppeteer

Puppeteer — библиотека с упором на chrome. Синтаксис очень похож на playwright:

// Подключаем библиотеку
import puppeteer from 'puppeteer';

describe(() => {
  test('Main test', async () => {
    // Запускаем браузер и получаем инстанс браузера
    const browser = await puppeteer.launch();
    // Получаем инстанс страницы
    const page = await browser.newPage();
    // Открываем страницу
    await page.goto('https://mail.ru');
    // Вызываем событие
    await page.click('[data-testid="enter-password"]');
    // Создаем скриншот
    await page.screenshot({ path: 'example.png' });

    // Закрываем браузер
    await browser.close();
  });
});

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»