Playwright, Selenium, Cypress, WebdriverIO: что реально известно о скорости в 2026 году (и как намерить свои цифры)

от автора

За последний месяц я насчитал минимум семь свежих статей с заголовком в духе «Playwright быстрее Selenium на N%». Проблема в том, что N у всех разный: 23%, 42%, 63%, «1.85x». Методология почти нигде не раскрыта дальше фразы «controlled environment». Для решения, которое определяет CI-бюджет и архитектуру тестов на годы вперёд, это не цифры — это шум.

Здесь — что из этого шума реально на что-то опирается, почему остальное несопоставимо, и рабочий бенчмарк-харнесс, который вы можете прогнать на своём стеке за один CI-job и получить именно свои числа, а не чужие проценты.

Почему вообще должна быть разница

Все четыре инструмента по-разному говорят с браузером, и это архитектурное решение — не деталь, а главная причина разницы в скорости.

Selenium работает через протокол WebDriver: команда из теста идёт HTTP-запросом к драйверу браузера (chromedriver/geckodriver), тот транслирует её в нативный вызов. Каждое действие — это запрос-ответ по HTTP, и эта связка добавляет задержку на каждом шаге. Selenium 4 добавил протокол BiDi (двунаправленный, поверх WebSocket) для части операций — сетевого перехвата, логов консоли, — но базовое исполнение команд по-прежнему идёт через HTTP-слой WebDriver.

Cypress в принципе не выходит за пределы браузера: тестовый раннер исполняется в том же event loop, что и тестируемое приложение, без какого-либо внешнего протокола между ними. Это даёт нулевую сетевую задержку на каждое действие, но ценой архитектурных ограничений — Cypress не может по-настоящему управлять вторым табом, потому что сам раннер занимает iframe рядом с приложением.

Playwright подключается к браузерам напрямую по их нативным debug-протоколам — Chrome DevTools Protocol для Chromium, патченый Marionette для Firefox, WebKit Inspector Protocol для WebKit — через постоянное WebSocket-соединение. HTTP-слоя между тестом и браузером нет в принципе.

WebdriverIO — особый случай: он может работать и через классический WebDriver-протокол (тогда архитектурно близок к Selenium), и через DevTools/BiDi-протокол. Поэтому в части бенчмарков WDIO попадает в категорию «быстрый», а в части — в категорию «как Selenium», и это зависит исключительно от того, какой драйвер выбрал автор теста.

Из этого уже можно сделать архитектурный вывод без единого замера: WebSocket-протоколы (Playwright, WDIO в DevTools-режиме) структурно не могут быть медленнее, чем HTTP-polling (классический WebDriver), при прочих равных. Вопрос не «будет ли разница», а «насколько она проявится на конкретном тест-сьюте».

Что из опубликованного действительно похоже на бенчмарк

Один из немногих источников, который раскрывает методологию — серия постов Checkly (компания, которая делает synthetic monitoring, то есть сама эксплуатирует эти инструменты в проде, а не продаёт TMS или дашборд для них). Они гоняли одинаковые E2E-сценарии против реальных сайтов на одинаковой инфраструктуре несколько тысяч раз подряд.

Несколько выводов оттуда, которые стоит учитывать:

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

  • При исполнении сьюта (а не одиночного теста) Cypress оказался медленнее самого быстрого инструмента (Playwright) примерно на 23%, и при этом лишь на ~3% медленнее, чем связка WebDriverIO+Selenium, которая в этом прогоне была самой медленной.

  • WebDriverIO в режиме DevTools-протокола показал заметно более высокую вариативность времени исполнения, чем остальные инструменты — то есть медиана может быть неплохой, а вот p95 — нет, и для CI это часто важнее средней цифры.

  • В реальных сценариях именно Playwright показал наименьшую вариативность времени исполнения среди всех протестированных инструментов, что для CI-стабильности значит больше, чем чистая скорость.

Отдельно, по доле рынка — это не бенчмарк скорости, но это реальные данные опроса, а не маркетинговый текст: по данным State of JS 2024, доля Playwright выросла с 9% в 2023 году до 15% в 2024-м, и опрос назвал его самой быстрорастущей по принятию технологией тестирования того года.

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

А вот это — нет

Дальше идёт ворох свежих (2026 год) постов от компаний, которые продают платформы вокруг этих фреймворков — TMS-аналитику, дашборды для CI, AI-ревью прогонов. Формально у них тоже написано «controlled benchmark», но цифры между собой не бьются, и это видно невооружённым глазом:

Источник

Заявленная разница (Playwright vs Selenium)

Как обоснована

TestDino (блог №1, февраль 2026)

«на ~290 мс/действие vs 536 мс у Selenium»

Без описания стенда и кол-ва прогонов

TestDino (блог №2, апрель 2026)

«на 42% быстрее»

Ссылка на «внутреннюю аналитику» компании

Techoral

«на 63% быстрее, на 82% меньше флака»

«1000 тестов», окружение не описано

Vervali Systems

«в 1.85 раза быстрее»

Без сырых данных

Разброс от 42% до 63% при формально одинаковой постановке вопроса — это не погрешность измерения, это разные (или вообще не проводившиеся) замеры, упакованные в уверенный тон. Часть этих текстов прямо признаётся, что данные «собраны из трёх источников», то есть это уже не бенчмарк, а агрегация чужих маркетинговых чисел, выданная за новое исследование. Использовать такие цифры в технической статье на Хабре — то же самое, что цитировать рекламную листовку как research paper.

Вывод: если у вас на проекте есть конкретный тест-сьют и конкретная CI-инфраструктура, единственное число, которому стоит доверять — то, которое вы получите сами. Дальше — харнесс для этого.

Бенчмарк-харнесс: один и тот же сценарий на четырёх инструментах

Идея: один и тот же flow (логин → переход в раздел → клик по трём элементам → проверка состояния), реализованный нативными средствами каждого фреймворка, прогнанный N раз подряд на одной и той же CI-машине, с фиксированными версиями браузеров.

Тестовое приложение

Берите свой реальный staging, либо локальный fixture-сервер на Express — главное, чтобы все четыре инструмента ходили в один и тот же инстанс с одинаковой задержкой сети (то есть localhost, не реальный CDN, иначе сетевой джиттер забьёт разницу между фреймворками).

// fixture-server.js — минимальное SPA-подобное приложение для замераconst express = require('express');const app = express();app.use(express.urlencoded({ extended: true }));app.get('/login', (_, res) => res.send(`  <form method="post" action="/login">    <input name="user" /><input name="pass" type="password" />    <button type="submit">Войти</button>  </form>`));app.post('/login', (_, res) => res.redirect('/dashboard'));app.get('/dashboard', (_, res) => res.send(`  <div id="items">    <button data-id="1">Item 1</button>    <button data-id="2">Item 2</button>    <button data-id="3">Item 3</button>  </div>  <div id="status">idle</div>  <script>    document.querySelectorAll('button').forEach(b =>      b.onclick = () => document.getElementById('status').innerText = 'clicked-' + b.dataset.id);  </script>`));app.listen(3000);

Playwright

// playwright.spec.tsimport { test, expect } from '@playwright/test';test('login + interact flow', async ({ page }) => {  await page.goto('http://localhost:3000/login');  await page.fill('input[name=user]', 'qa');  await page.fill('input[name=pass]', 'qa');  await page.click('button[type=submit]');  await page.waitForURL('**/dashboard');  for (const id of [1, 2, 3]) {    await page.click(`button[data-id="${id}"]`);  }  await expect(page.locator('#status')).toHaveText('clicked-3');});
npx playwright test --reporter=json --workers=1 --repeat-each=50 > pw-results.json

Cypress

// cypress/e2e/flow.cy.jsdescribe('login + interact flow', () => {  it('runs the flow', () => {    cy.visit('http://localhost:3000/login');    cy.get('input[name=user]').type('qa');    cy.get('input[name=pass]').type('qa');    cy.get('button[type=submit]').click();    cy.url().should('include', '/dashboard');    [1, 2, 3].forEach((id) => cy.get(`button[data-id="${id}"]`).click());    cy.get('#status').should('have.text', 'clicked-3');  });});
npx cypress run --reporter json --spec cypress/e2e/flow.cy.js > cy-results.json

WebdriverIO (в DevTools/Bidi-режиме — для честного сравнения с Playwright)

// wdio.flow.spec.jsdescribe('login + interact flow', () => {  it('runs the flow', async () => {    await browser.url('http://localhost:3000/login');    await $('input[name=user]').setValue('qa');    await $('input[name=pass]').setValue('qa');    await $('button[type=submit]').click();    await browser.waitUntil(async () => (await browser.getUrl()).includes('/dashboard'));    for (const id of [1, 2, 3]) {      await $(`button[data-id="${id}"]`).click();    }    await expect($('#status')).toHaveText('clicked-3');  });});

Selenium (Node binding, классический WebDriver-протокол)

// selenium.flow.jsconst { Builder, By, until } = require('selenium-webdriver');(async () => {  const driver = await new Builder().forBrowser('chrome').build();  const t0 = performance.now();  try {    await driver.get('http://localhost:3000/login');    await driver.findElement(By.name('user')).sendKeys('qa');    await driver.findElement(By.name('pass')).sendKeys('qa');    await driver.findElement(By.css('button[type=submit]')).click();    await driver.wait(until.urlContains('/dashboard'), 5000);    for (const id of [1, 2, 3]) {      await driver.findElement(By.css(`button[data-id="${id}"]`)).click();    }    await driver.wait(until.elementTextContains(driver.findElement(By.id('status')), 'clicked-3'), 5000);  } finally {    console.log(JSON.stringify({ duration_ms: performance.now() - t0 }));    await driver.quit();  }})();

Для Selenium придётся самостоятельно обернуть замер времени (как выше) — встроенного репортера с таймингами на уровне теста, как у остальных трёх, нет из коробки.

Условия, без которых сравнение бессмысленно

  • Один и тот же раннер: ubuntu-22.04, фиксированное число vCPU/RAM (GitHub Actions: ubuntu-latest, без переменной нагрузки от соседних джобов в матрице).

  • Headless-режим везде — иначе сравниваете не фреймворки, а рендеринг GUI.

  • Фиксированные, явно прописанные версии браузеров (npx playwright install --with-deps chromium@<version>, chromedriver той же мажорной версии, что и Chrome в раннере) — рассинхрон версий браузера и драйвера у Selenium даёт случайный шум, который легко спутать с архитектурной разницей.

  • Минимум 30–50 повторов на инструмент, отчёт по медиане и p95, а не по одному прогону — отдельные запуски шумят на 20–40% даже в рамках одного фреймворка.

  • Один и тот же flow, написанный максимально close-to-idiomatic для каждого инструмента (не нужно искусственно занижать Selenium через явные sleep(), которые никто в проде не пишет — но и не нужно подсовывать ему оптимизации, недоступные в реальном коде команды).

Как считать результат

node -e "const fs = require('fs');const runs = JSON.parse(fs.readFileSync(process.argv[1])).map(r => r.duration_ms).sort((a,b)=>a-b);const median = runs[Math.floor(runs.length/2)];const p95 = runs[Math.floor(runs.length*0.95)];console.log({ median, p95, runs: runs.length });" results.json

Прогоните так каждый инструмент трижды в разные дни (CI-раннеры — общий ресурс, шум от соседей реален), сравните медианы и p95 между прогонами одного и того же фреймворка — если они расходятся больше чем на 10–15%, ваш стенд недостаточно стабилен для выводов, и сначала нужно разбираться с этим, а не с выбором фреймворка.

Что можно сказать без замеров, и что нельзя

Без единого личного замера, опираясь только на архитектуру и на источники с раскрытой методологией, можно уверенно сказать:

  • WebSocket-протоколы (Playwright, WDIO в BiDi-режиме) структурно быстрее на каждое отдельное действие, чем HTTP-polling классического WebDriver. Это не зависит от вендора и не может «развернуться» на каком-то конкретном стенде.

  • На сьютах (а не одиночных тестах) разрыв между Cypress и Playwright меньше, чем на коротких прогонах — стартовые издержки амортизируются.

  • Стабильность (variance, p95) для CI часто важнее средней скорости — это явно показано в данных Checkly, и это игнорируют почти все вендорские посты, концентрируясь на одном эффектном проценте в заголовке.

Чего нельзя сказать без своего замера — это любое конкретное «X% быстрее» для вашего стека. Слишком много переменных: размер DOM, количество сетевых запросов на странице, тип selectors (CSS vs XPath vs role-based), параллелизм в CI, версии браузеров. Те же 23% у Checkly и 63% у Techoral — это не противоречие в «правде», это просто два разных приложения и два разных метода счёта.

Резюме

Если вам нужно решение «на сейчас» без замеров — архитектура даёт достаточно сигнала: для нового проекта на вебе Playwright или WDIO в BiDi-режиме — разумный default по скорости и стабильности, Cypress — если важнее DX для фронтенд-команды и сьюты достаточно длинные, чтобы амортизировать старт, Selenium — если у вас уже большая инвестиция в Java/C#/Ruby-инфраструктуру и WebDriver Grid, который работает.

Если решение нужно обосновать цифрами перед руководством — не берите процент из чужого блога. Прогоните харнесс выше на своём стеке, на своём приложении, в своём CI — это займёт один рабочий день и даст вам единственное число, которому вы сможете доверять и которое сможете защитить, если кто-то спросит «а как считали».

ссылка на оригинал статьи https://habr.com/ru/articles/1050168/