Pull to refresh

5 причин почему Playwright лучше Cypress

Reading time6 min
Views13K
Original author: Alister Scott

Перевод статьи Alister ScottFive reasons why Playwright is better than Cypress

Очевидно, что я не фанат Cypress, о чем, собственно, 2 года назад написал пост. За это время мое внимание привлек Playwright. Я специально вернулся к Cypress, чтобы сравнить его с Playwright и посмотреть, были ли исправлены претензии по отношению к первому фреймворку. На основании проведенного сравнения, могу смело рассказать о причинах, почему Cypress все еще проигрывает конкуренцию.

Сравниваемые версии:
Cypress: 8.7.0 on Electron 93
Playwright: 1.16.0

Причина 1: Playwright быстрее чем Cypress

Зафиксированный результат — до 4-х раз быстрее!
Берем простой пример из документации Cypress:

describe('My First Test', () => {
    it('clicking "type" shows the right headings', () => {
      cy.visit('https://example.cypress.io')
      cy.contains('type').click()
      // Should be on a new URL which includes '/commands/actions'
      cy.url().should('include', '/commands/actions')
      // Get an input, type into it and verify that the value has been updated
      cy.get('.action-email')
        .type('fake@email.com')
        .should('have.value', 'fake@email.com')
    })
})

Работает 8 секунд на моем M1 Macbook Air

✔  demo-spec.js                             
00:08

Тот же пример на Playwright:

import { test, expect } from '@playwright/test'
 
test.describe.parallel('My First Test', () => {
  test('clicking "type" shows the right headings', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    // Should be on a new URL which includes '/commands/actions'
    await page.waitForURL(/.+\/commands\/actions$/)
    // Get an input, type into it and verify that the value has been updated
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
})
Running 1 test using 1 worker
  ✓  scenarios/compare.cypress.spec.ts:4:3 › My First Test › clicking "type" shows the right headings (1s)
  1 passed (2s)

В 4 раза быстрее!😎

Причина 2: У Playwright крутая поддержка для параллельного запуска тестов на одной машине (локально и на CI без подписки или учетной записи)

Чтобы запустить тесты параллельно, нужно отправить их в Cypress Dashboard (или Sorry Cypress). Только так возможна работа в несколько потоков на одной машине, хотя разработчики Cypress и не рекомендуют заниматься подобным. В то же время, на Playwright параллельные тесты запускаются без проблем.

“While parallel tests can also technically run on a single machine, we do not recommend it since this machine would require significant resources to run your tests efficiently.”

https://docs.cypress.io/guides/guides/parallelization#Overview

Либо Cypress так раздут, что для параллельных тестов требуется дорогое оборудование, либо команда Cypress хочет, чтобы пользователи платили за их службу параллельного выполнения. А может быть, и то, и другое?

Причина 3: Playwright поддерживает параллельные тесты в одном файле, а для Cypress их придется разделять

Например, в Playwright я могу запустить эти 8 тестов параллельно:

import { test, expect } from '@playwright/test'
 
test.describe.parallel('My First Test', () => {
  test('clicking "type" shows the right headings 1', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
  test('clicking "type" shows the right headings 2', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
  test('clicking "type" shows the right headings 3', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
  test('clicking "type" shows the right headings 4', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
  test('clicking "type" shows the right headings 5', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
  test('clicking "type" shows the right headings 6', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
  test('clicking "type" shows the right headings 7', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
  test('clicking "type" shows the right headings 8', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
})

на моем Macbook Air занимает всего 6 секунд 😎

npx playwright test "./scenarios/compare.cypress.bulk.spec.ts"
Running 8 tests using 4 workers
  ✓  scenarios/compare.cypress.bulk.spec.ts:4:3 › My First Test › clicking "type" shows the right headings 1 (4s)
	......
8 passed (6s)

Чтобы запустить эти тесты параллельно (с использованием сервера) в Cypress, мне нужно было бы структурировать их как отдельные файлы (по одной спецификации на каждый):

И чтобы провести эти тесты, на той же машине потребуется 45 секунд, что в 7,5 раз медленнее.

Причина 4: Playwright полностью поддерживает синтаксис async/await для чистого и читаемого кода

import { test, expect } from '@playwright/test'
 
test.describe.parallel('My First Test', () => {
  test('clicking "type" shows the right headings', async ({ page }) => {
    await page.goto('https://example.cypress.io')
    await page.click('a:has-text("type")')
    await page.waitForURL(/.+\/commands\/actions$/)
    await page.fill('.action-email', 'fake@email.com')
    await expect(page.locator('.action-email')).toHaveValue('fake@email.com')
  })
})

Нет цепочек прототипов, никакого сложного для понимания кода, никакой магии. Вы можете видеть, откуда берутся test, expect и page.

Тот же тест в Cypress:

describe('My First Test', () => {
    it('clicking "type" shows the right headings', () => {
      cy.visit('https://example.cypress.io')
     
      cy.contains('type').click()
   
      // Should be on a new URL which includes '/commands/actions'
      cy.url().should('include', '/commands/actions')
   
      // Get an input, type into it and verify that the value has been updated
      cy.get('.action-email')
        .type('fake@email.com')
        .should('have.value', 'fake@email.com')
    })
})

Обратите внимание, как функция вызывает цепочку. Неплохая реализация в данном примере, но происходящее быстро выходит из-под контроля. Нет никаких указаний, что это синхронный код — отсутствует await на вызовы функций. “Магия” происходит в случае с cy и describe, и при этом совершенно отсутствует импорт.

Причина 5. Playwright не нужны плагины

В Cypress много ограничений. Большую часть из них можно исправить плагинами, но некоторые проблемы таковыми остаются ввиду отсутствия “лекарства”.

Нужно нажать клавишу tab? Для этого есть плагин. И, судя по ишью Github он вообще не работает из-за отсутствия поддержки этой самой клавиши. Тема открыта с 2016 года по сей день. Вопрос актуален в течение всего времени с момента, когда я последний раз писал о Cypress.

Нужно загрузить файл? Для этого также есть плагин.

Вам нужно выполнить тесты n раз подряд, чтобы проверить стабильность тестов? Для этого есть плагин.

К счастью, Playwright изначально поддерживает всё вышеописанное и множество других функций без каких-либо плагинов. 😎

Вывод:

Как видите, я все еще не фанат Cypress 😀

Тем более теперь есть инструмент, который стал намного лучше и доступен совершенно бесплатно.

Так что я буду придерживаться вывода двухлетней давности:

Издалека Cypress выглядит как идеальный инструмент для автоматизированного тестирования. Я думаю, что его неправильно позиционируют, как инструмент е2е тестирования, хотя на самом деле фреймворк годится только для проверки компонентов. В нем слишком много ограничений, чтобы заниматься созданием настоящих e2e тестов.
https://alisterbscott.com/2019/07/06/my-oughtts-on-cypress-io/

Tags:
Hubs:
+25
Comments6

Articles

Change theme settings