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

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