By Stanislav Taran

Networking Throttle в Playwright


Вступ

В сучасному світі швидкого інтернету швидкість завантаження веб-додатка може здатися другорядною. Але для додатків, якими користуються по всьому світу, в різних місцевостях, тестування продуктивності за різних мережевих умов є досить важливим. Напевно, ви всі знаєте, як обмежити швидкість мережі в браузері. Використовуючи DevTools, ви легко можете симулювати різні мережеві умови.

Network Throttling Presets

Але що, якщо ви хочете зробити це програмно, у ваших автотестах? У цій статті я покажу вам, як це зробити, використовуючи Playwright.

Вибірковий Throttling

Уявімо, що у вас є додаток, який агрегує дані з різних джерел. Ви хочете перевірити, як ваш додаток поводиться, коли одне з джерел відповідає повільно. Це легко реалізувати.

const DELAY_MS = 5_000;

test.describe('Network Throttle with delay', () => {
  test('defer particular requests with predefined time', async ({ page }) => {
    await page.route('todomvc/js/*.js', async route => {
      await new Promise(resolve => setTimeout(resolve, DELAY_MS));
      await route.continue();
    });

    await page.goto('/todomvc');
    await page.waitForLoadState('domcontentloaded');
  });
})

В цьому прикладі ми “затриуємо” всі запити до ендпоінтів по патерну todomvc/js/*.js на 5 секунд. В той же час інші запити будуть оброблятися як зазвичай.

Тепер перейдемо до більш цікавої частини.

Chrome DevTools Protocol

Playwright використовує Chrome DevTools Protocol (CDP) для взаємодії з браузером. Це низькорівневий протокол дебагіннгу, який використовується в Chrome DevTools. Він дозволяє керувати браузером, інспектувати DOM, дебажити JavaScript та інше.

Ви можете відвідати Chrome DevTools Protocol щоб побачити всі доступні команди.

В чому ми зараз зацікавлені - це домен Network. Він дозволяє відстежувати мережеві активності сторінки. Він надає інформацію про запити та відповіді тощо.

Як ви можете бачити, він також має метод під назвою Network.emulateNetworkConditions, який дозволяє емулювати мережеві умови.

Попередження: Все, що я покажу вам, працюватиме тільки в браузерах на основі Chromium. Це включає Chrome, Edge, мобільні версії цих браузерів і т.д. Це не буде працювати в Firefox або WebKit.

Емуляція мережевих умов

Тож, ми маємо метод, який дозволяє нам емулювати мережеві умови. Але як ми його використовуємо? Куди ми відправляємо цю команду? Завдяки Playwright, ми можемо легко відправляти команди CDP, використовуючи CDPSession (Playwright API).

Перед тим, як ми почнемо кодити, давайте визначимо ціль, яку ми хочемо досягти. Ми хочемо обмежити мережу до 2G, 3G, 4G та деяких інших швидкостей і перевірити, скільки часу потрібно для завантаження сторінки та виконання певної дії.

Перш за все, як ви бачите, нам потрібно передати такі обов’язкові параметри методу Network.emulateNetworkConditions.

  • offline: True для емуляції відключення від Інтернету.
  • latency: Мінімальна затримка від відправлення запиту до отримання заголовків відповіді (мс).
  • downloadThroughput: Максимальна загальна пропускна здатність для завантаження (байт/с). -1 вимикає обмеження завантаження.
  • uploadThroughput: Максимальна загальна пропускна здатність для відвантаження (байт/с). -1 вимикає обмеження відвантаження.

Я думаю, ми також можемо використати один необов’язковий параметр connectionType, який може мати наступні значення: none, cellular2g, cellular3g, cellular4g, bluetooth, ethernet, wifi, wimax, other.

Але які значення ми повинні передати для цих параметрів? Фактично, ці значення можуть відрізнятися в залежності від багатьох факторів.

Провівши певні дослідження, я знайшов цей StackOverflow пост, де користувач надає наступний скріншот DevTools: Він старенький, але я думаю, що він все ще більш-менш актуальний.

Якщо вам цікаві найновіші значення для Chrome DevTools, ви можете переглянути їх тут source code.

Network Throttling Presets

Давайте створимо словник з деякими з цих значень.

export const NETWORK_PRESETS = {
    Offline: {
        offline: true,
        downloadThroughput: 0,
        uploadThroughput: 0,
        latency: 0,
        connectionType: 'none',
    },
    NoThrottle: {
        offline: false,
        downloadThroughput: -1,
        uploadThroughput: -1,
        latency: 0,
    },
    Regular2G: {
        offline: false,
        downloadThroughput: (250 * 1024) / 8,
        uploadThroughput: (50 * 1024) / 8,
        latency: 300,
        connectionType: 'cellular2g',
    },
    Good2G: {
        offline: false,
        downloadThroughput: (450 * 1024) / 8,
        uploadThroughput: (150 * 1024) / 8,
        latency: 150,
        connectionType: 'cellular2g',
    },
    Regular3G: {
        offline: false,
        downloadThroughput: (750 * 1024) / 8,
        uploadThroughput: (250 * 1024) / 8,
        latency: 100,
        connectionType: 'cellular3g',
    },
    Good3G: {
        offline: false,
        downloadThroughput: (1.5 * 1024 * 1024) / 8,
        uploadThroughput: (750 * 1024) / 8,
        latency: 40,
        connectionType: 'cellular3g',
    },
    Regular4G: {
        offline: false,
        downloadThroughput: (4 * 1024 * 1024) / 8,
        uploadThroughput: (3 * 1024 * 1024) / 8,
        latency: 20,
        connectionType: 'cellular4g',
    },
    WiFi: {
        offline: false,
        downloadThroughput: (30 * 1024 * 1024) / 8,
        uploadThroughput: (15 * 1024 * 1024) / 8,
        latency: 2,
        connectionType: 'wifi',
    }
};

Що ми маємо наразі:

  1. Ми знаємо команду CDP, яку нам потрібно відправити в браузер.
  2. Ми знаємо, як відправити цю команду, використовуючи Playwright.
  3. У нас є словник з деякими мережевими пресетами, які ми використаємо як параметри для команди.

Думаю, цього достатньо для теорії. Давайте перейдемо до практики і створимо простий тестовий сценарій.

Тестовий сценарій

Ми створимо тест, який відкриває додаток “Playwright TODO” та додає новий елемент todo.

import { test, expect } from '@playwright/test';
import {NETWORK_PRESETS} from "../src/data/networkPresets";

test.describe.only('Network Throttle emulation', () => {
    for (const [name, value] of Object.entries(NETWORK_PRESETS)) {
    test(`emulate network throttle "${name}"`, async ({ context, page }) => {
      const cdpSession = await context.newCDPSession(page)
      await cdpSession.send('Network.emulateNetworkConditions', value)

      await page.goto('/todomvc');
      await page.waitForLoadState('domcontentloaded');

      await page.locator('.new-todo').fill("Network Throttle");
      await page.keyboard.press('Enter');
    });
    }

    test(`No throttle"`, async ({page }) => {
        await page.goto('/todomvc');
        await page.waitForLoadState('domcontentloaded');

        await page.locator('.new-todo').fill("Network Throttle");
        await page.keyboard.press('Enter');
    });
})

Я використовую параметризовані тести, щоб запустити один і той же тестовий сценарій з різними мережевими пресетами. Також я встановив опцію repeatEach на 20, щоб ми могли побачити різницю в тривалості виконання тесту.

Якщо вам цікаво чому я також додав тест без будь-якого обмеження, я покажу вам пізніше деякі цікаві результати, в бонусному розділі.

Результати

Я запустив тест на своїй локальній машині і ось результати, скільки часу (у мілісекундах) знадобилося для виконання тестового сценарію за різних мережевих умов:

All Network Throttling Presets

Насправді тут немає нічого дивного. Чим повільніша мережа, тим більше часу потрібно для виконання тестового сценарію. Але мета досягнута. Ми можемо легко обмежити мережу, використовуючи Playwright.

Ви можете визначити таймаути для ваших тестів, щоб зафейлити їх, якщо вони займають занадто багато часу на виконання.

Висновок

Навіть якщо швидкість мережі може здатися другорядною, буває важливим протестувати ваш додаток за різних мережевих умов. Але в той же час, вам слід враховувати час, необхідний для виконання таких тестів. Чекати години на результати - не найкраща ідея. Тому обмеження мережі може бути корисним інструментом, але використовуйте його розумно.

Бонус

В цій секції я покажу вам деякі цікаві результати, які я отримав під час виконання тестового сценарію за різних мережевих умов.

Погляньте на наступну діаграму. Вона показує різницю в тривалості тесту між тестом “No throttle” та тестом з емуляцією пресету “NoThrottle”.

No Throttle vs NoThrottle

Як ви можете бачити, “емульований” пресет NoThrottle повільніший, ніж не емульований тест “No throttle”. Думаю, ми можемо це звернути на те що відправка команди CDP займає певний час і виконання цієї команди в браузері також займає певний час.

Що ж, це може пояснити, чому тест “No throttle” завжди швидший, ніж тест з емуляцією пресету “NoThrottle” (насправді тільки частково).

Але чому перші три “емульовані” тести пресету NoThrottle зайняли набагато більше часу, ніж всі інші? Я кілька разів пробував запускати тести і отримував ту ж саму ситуацію.

У мене немає відповіді на це питання. Якщо у вас є які-небудь ідеї, будь ласка, поділіться ними в нашій групі в Telegram.

І я хотів би показати вам ще одну цікаву діаграму. Вона показує різницю в тривалості тесту між тестом “No throttle” та тестом з емуляцією пресету “WiFi”.

No Throttle vs WiFi

Якщо є додаткова затримка через відправку команди CDP, чому пресет “WiFi” 8 з 20 разів був швидшим, ніж тест “No throttle”? Тест “No throttle” використовує реальні мережеві умови, тому він повинен бути швидшим, ніж тест з емуляцією пресету “WiFi”, оскільки емуляція пресету “WiFi” також повинна додати деяку затримку.

Перед тим, як запустити тести, я вимірював швидкість моєї мережі використовуючи speedtest.net.

My WiFi Speed

Дуже цікаво подумати. Можливо, варто дослідити це питання докладніше, але це виходить за рамки цієї статті.

Ви також можете знайти вихідний код цієї статті на GitHub.

Дякую за те що дочитали до кінця!