В этой статье я кратко расскажу о тестировании, а затем углублюсь в сквозное тестирование с использованием Jest и Puppeteer, которые являются очень популярными фреймворками Javascript, используемыми для целей тестирования.

Требования к этому руководству:

  1. Базовые знания Javascript
  2. Вы знаете, как использовать CLI
  3. Вы знакомы с Node.js и NPM
  4. Также могут пригодиться некоторые знания HTML и CSS.

Итак, позвольте мне сначала сказать несколько слов о тестировании в целом. Тестирование - важная часть каждого процесса разработки программного обеспечения. Это может значительно снизить стоимость вашего проекта и повысить продуктивность вашей команды. В основном есть три основных типа тестов:

В этом руководстве мы сосредоточимся на тестировании E2E, как следует из названия. Мы будем писать наши тесты с помощью двух мощных инструментов - Jest и Pupeeteer:

  • Jest: Полнофункциональный фреймворк для тестирования, разработанный Facebook. Он требует очень небольшой настройки и работает практически сразу после установки.
  • Puppeteer: библиотека Node.js, созданная Google, которая предоставляет удобный API для управления Headless Chrome.

И последнее, что нам понадобится, это пресет-шут-кукловод, который позволит нам объединить эти два фреймворка вместе.

Итак, что вы узнаете в итоге?

  1. Как интегрировать кукловод с Jest
  2. Формы тестирования
  3. Тестирование внешнего интерфейса в целом
  4. Снимаем скриншот
  5. Эмуляция мобильного устройства
  6. Перехват запросов
  7. Как настроить таргетинг на недавно открытую страницу в безголовом браузере
  8. И как отлаживать тесты

Настройка проекта

Для начала вам необходимо скачать или клонировать подготовленный мною проект GitHub Starter Project. Если вы не предпочитаете писать код, вы можете скачать уже готовый проект GitHub Final Project.

После загрузки Starter Project:

  • cd в репозиторий
cd <your_path>/E2E_Testing_with_Puppeteer_Starter
  • установить зависимости
npm install
  • запустить проект
npm run dev-server

Отлично, теперь наше приложение работает на http: // localhost: 8080. После его посещения вы должны увидеть что-то вроде этого:

Следующее, что нужно сделать, это установить все необходимые инструменты.

npm install puppeteer jest jest-puppeteer

Также нам нужно будет дополнительно установить jest-cli глобально, чтобы иметь возможность запускать только один тест отдельно от других.

npm install -g jest-cli

Первый взгляд на кукловода

Давайте запустим Puppeteer впервые самостоятельно, так что вы действительно увидите, как он работает в одиночку, без Jest. В корне проекта вы найдете puppeteer_firts_try.js файл, который содержит базовые инструкции для Puppeteer.

Запустите его с помощью:

node puppeteer_firts_try.js
  • in puppeteer_firts_try.js
const puppeteer = require('puppeteer');

(async function main(){
    try {
        const browser = await puppeteer.launch({ headless: false });
        const page = await browser.newPage();
        await page.goto('http://localhost:8080', {waitUntil: 'domcontentloaded'});
        
        await new Promise(resolve =>  setTimeout(resolve, 5000));
        console.log('done');
        await browser.close();
    } catch (e) {
        console.log('Err', e);
    }
})();

Как вы, наверное, уже заметили, Puppeteer в значительной степени полагается на обещания, поэтому мы всегда будем использовать его с async / await. С помощью puppeteer.launch () в строке 5 мы запускаем новый экземпляр Chromium, в параметрах мы указываем headless: false, что означает, что браузер не будет работать в автономном режиме (в основном без графического пользовательского интерфейса). В следующей строке мы открываем новую страницу, а затем в строке 7 переходим к http: // localhost: 8080. waitUntil: 'domcontentloaded' в строке 7 указывает, что наш код будет ждать загрузки содержимого DOM. Строка 9 просто заставляет приложение останавливаться на 5 секунд, чтобы вы могли наблюдать за ним. И в строке 11 закрываем браузер.

Интеграция Puppeteer с Jest

Теперь мы интегрируем Puppeteer с Jest. Но зачем это вообще нужно? Мы делаем это, потому что Puppeteer сам по себе не является фреймворком для тестирования, это инструмент, который позволяет нам управлять Headless Chrome. Поэтому, чтобы упростить нашу работу, мы объединяем его с Jest, который предоставляет отличные утилиты для тестирования.

Конфигурация Jest

  • создайте jest.config.js файл в корне проекта и вставьте этот код в:
module.exports = {
    preset: "jest-puppeteer",
    globals: {
      URL: "http://localhost:8080"
    },
    testMatch: [
      "**/test/**/*.test.js"
    ],
    verbose: true
}

В строке 2 мы указываем пресет jest-puppeteer, который позволит нам использовать Jest с Puppeteer. В globals мы объявляем переменные, которые будут доступны во всем нашем наборе тестов. А в testMatch мы только говорим, в какой папке и какие файлы следует искать Jest.

Конфигурация пресета шут-кукловод

  • создайте jest-puppeteer.config.js файл в корне проекта и используйте этот код:
module.exports = {
    launch: {
        headless: process.env.HEADLESS !== 'false',
        slowMo: process.env.SLOWMO ? process.env.SLOWMO : 0,
        devtools: true
    }
}

В объекте lauch мы можем указать параметры для экземпляра Chromium, который будет запускаться перед запуском наших тестов и который будет доступен для всех наших тестовых файлов. Здесь вы можете указать все параметры, которые обычно передаются в puppeteer.launch (). В строке 3 мы указываем, должен ли Puppeteer запускать браузер в режиме headless или нет. А в строке 4 мы заставляем Puppeteer работать в slowMo, что замедляет Puppeteer на указанные нами миллисекунды. Так мы сможем наблюдать, что он делает на самом деле. Оба эти варианта отлично подходят для отладки.

Написание наших тестов

Когда все настроено, мы можем наконец приступить к написанию тестов. Начнем с простого.

Тестирование внешнего интерфейса

  • в src/test/ вы найдете файл с именем frontend.test.js, в который вам нужно записать этот код:
const timeout = process.env.SLOWMO ? 30000 : 10000;

beforeAll(async () => {
    await page.goto(URL, {waitUntil: 'domcontentloaded'});
});

describe('Test header and title of the page', () => {
    test('Title of the page', async () => {
        const title = await page.title();
        expect(title).toBe('E2E Puppeteer Testing');
        
    }, timeout);
});

Теперь вы можете запустить:

npm run test

И вы должны увидеть что-то вроде этого:

Давайте проанализируем этот код построчно. В первой строке мы устанавливаем переменную timeout, которую мы позже используем для указания тайм-аута для наших тестов (обратите внимание, что мы указываем этот тайм-аут в миллисекундах). Итак, как вы можете видеть, если мы запускаем Puppeteer в slowMo, мы увеличиваем время ожидания с 10000 мс до 30000 мс. Это гарантирует, что время ожидания наших тестов не истечет. В строке 3 мы используем beforeAll, эта функция выполнит некоторый код перед выполнением всех тестов в нашем файле. Мы передаем этой функции асинхронный обратный вызов, в котором мы переходим к URL, который мы указали ранее как глобальную переменную. Но откуда мы взяли pagevariable? page фактически отображается в каждом тестовом файле в нашем наборе тестов благодаря предустановке jest-puppeteer. В строке 7 мы используем description, который позволяет нам группировать тесты вместе. А затем мы пишем наш настоящий тест. Этот тест довольно прост: в строке 9 мы получаем заголовок страницы, а затем используем встроенную в Jest библиотеку утверждений expect для проверки правильности заголовка.

Теперь давайте добавим в этот файл еще один тест. Вставьте этот код прямо под первым тестом в нашем блоке описания:

test('Header of the page', async () => {
        const h1Handle = await page.$('.learn_header');
        const html = await page.evaluate(h1Handle => h1Handle.innerHTML, h1Handle);

        expect(html).toBe("What will you learn");
    }, timeout);

В строке 2 мы используем page. $ (), Который позволяет нам выбирать элемент HTML с помощью обычного селектора CSS. И он возвращает ElementHandle, который мы позже можем использовать для получения innerHTML этого элемента. Затем в строке 3 мы используем page.evaluate (), который оценивает функцию в контексте страницы, и таким образом мы в основном получаем доступ к innerHTML нашего ElementHandle.

Формы Тесты

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

  • переименуйте form.test.js.example в src/test в form.test.js и вставьте этот код для описания блока, который там уже есть:
test('Submit form with valid data', async () => {
        await page.click('[href="/login"]');
        
        await page.waitForSelector('form');
        await page.type('#name', 'Rick');

        await page.type('#password','szechuanSauce');
        await page.type('#repeat_password','szechuanSauce');

        await page.click('[type="submit"]');
        await page.waitForSelector('.success');
        const html = await page.$eval('.success', el => el.innerHTML);

        expect(html).toBe('Successfully signed up!');
    }, timeout);

Итак, первое, что мы делаем в этом фрагменте, - нажимаем на ссылку входа в систему навигации. Для этого мы используем page.click (), который принимает в качестве аргумента селектор CSS. Поскольку мы перешли по другому URL-адресу, мы используем page.waitForSelector (), чтобы дождаться, пока наша форма будет отрисована DOM, чтобы мы могли начать что-то с ней делать. Затем мы используем метод page.type () для заполнения нашей формы, этот метод принимает два аргумента: селектор CSS и текст, который мы хотим ввести. Затем мы отправляем нашу форму, ожидаем появления сообщения об успешном завершении и получаем его innerHTML с помощью page. $ Eval ().

Если вы сейчас запустите npm run test, у вас должно быть три проходящих тета.

Создание снимков экрана на компьютере и мобильном устройстве

Протестировав нашу форму и интерфейс, мы можем обратить внимание на создание снимков экрана и эмуляцию мобильных устройств в Puppeteer.

  • переименуйте screenshots.test.js.example в src/test в screenshots.test.js и вставьте этот код для описания блока, который уже закодирован там:
test('Take screenshot of home page', async () => {
        await page.setViewport({ width: 1920, height: 1080 });
        await page.screenshot({
            path: './src/test/screenshots/home.jpg',
            fullpage: true,
            type: 'jpeg'
        });
    }, timeout);

В этом блоке кода мы сначала устанавливаем область просмотра страницы с помощью page.setViewport (), а затем делаем снимок экрана с page.screenshot (), для которого мы предоставляем некоторые параметры, чтобы указать, где хранить наше изображение, и в каком формате его хранить.

После запуска npm run test вы сможете найти изображение с именем home.jpg в test/screenshots папке.

Теперь сделаем снимок экрана при эмуляции мобильного устройства. Сначала добавьте эту строку кода вверху файла:

const devices = require('puppeteer/DeviceDescriptors');

А затем добавьте этот тест в наш блок описания:

test('Emulate Mobile Device And take screenshot', async () => {
    await page.goto(`${URL}/login`, {waitUntil: 'domcontentloaded'})
    const iPhonex = devices['iPhone X'];
    await page.emulate(iPhonex);
    await page.setViewport({ width: 375, height: 812, isMobile: true});
    await page.screenshot({
        path: './src/test/screenshots/home-mobile.jpg',
        fullpage: true,
        type: 'jpeg'
    });
}, timeout);

Этот код похож на предыдущий. Разница в том, что сейчас требуются устройства от puppeteer/DeviceDescriptors. Затем мы получаем доступ к iPhone X в строке 3 из объекта Устройства. В следующей строке мы эмулируем это устройство с помощью page.emulate (). А затем мы просто делаем снимок экрана, как в предыдущем фрагменте кода.

Запрос на перехват и таргетинг на недавно открытую страницу

Итак, теперь мы рассмотрим некоторые более продвинутые функции, такие как перехват запросов. И я также расскажу, как настроить таргетинг на недавно открытую открытую страницу в безголовом браузере.

  • переименуйте general.test.js.example в src/test в general.test.js и скопируйте и вставьте туда этот фрагмент кода:
test('Intercept Request', async () => {
        await page.setRequestInterception(true);
        page.on('request', interceptedRequest => {
            if (interceptedRequest.url().endsWith('.png')) {
                interceptedRequest.abort();
            } else {
                interceptedRequest.continue();
            }
        });
        await page.reload({waitUntil: 'networkidle0'});
        // await jestPuppeteer.debug();
        await page.setRequestInterception(false);
    }, timeout);

Здесь в первой строке мы устанавливаем page.setRequestInterception () равным true, что позволяет нам перехватывать исходящие запросы. В строках 3–9 мы говорим Puppeteer прерывать каждый запрос, который заканчивается на '.png'. Таким образом, благодаря этому наша страница не сможет загрузить изображение, которое в настоящее время находится на главной странице, по крайней мере, после перезагрузки страницы, потому что изображение было загружено до того, как мы установили перехват запроса. Затем в строке 10 мы перезагрузим нашу страницу с помощью page.reload (), чтобы мы могли видеть, что изображение не отображается. Но как на самом деле, когда тесты Puppeteer такие быстрые? Для этого предназначен закомментированный код в следующей строке, но я вернусь к этому в разделе отладки. А в строке 12 мы устанавливаем page.setRequestInterception () равным false, что очень ВАЖНО! Потому что, если вы не установите его на false, перехват запроса будет установлен на true для всех других тестов, которые идут после этого, и это может вызвать у вас много проблем!

Теперь давайте добавим наш последний тест, с помощью которого я покажу вам, как можно настроить таргетинг на недавно открытую страницу с помощью Puppeteer в браузере без заголовка.

  • добавьте этот тест в наш блок описания:
test('Target newly opened page', async () => {
        const newPagePromise = new Promise(resolve => browser.once('targetcreated', target => resolve(target.page())));
        await page.click('.repo_link');

        const newPage = await newPagePromise;
        const title = await newPage.title();
        await newPage.close();

        expect(title).toBe('GitHub - Zovi343/E2E_Testing_with_Puppeteer_Final');
    }, timeout);

В строке 2 мы создаем новое обещание, в котором мы слушаем с помощью browser.on (‘ targetcreated ’), если была создана новая цель (page). Мы снова можем получить доступ к browser, потому что он доступен нам благодаря предустановке jest-puppeteer. Затем мы нажимаем ссылку на домашней странице, которая открывает новую вкладку и указывает нам на GitHub Starter Project. В строке 7 мы ожидаем Promise, который мы создали в строке 2, и это Promise возвращает вновь открытую страницу. Итак, в конце концов, мы можем получить заголовок этой недавно открытой страницы и сделать наши утверждения.

Отладка вашего теста

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

Опции Headless и SlowMo

Итак, для отладки вы хотите запустить Puppeteer в режиме без головы, а также в замедленном режиме, чтобы вы могли действительно видеть, что происходит. Поскольку мы установили эти две опции в jest-puppeteer.config.js, все, что нам нужно сделать, это установить две переменные среды при запуске наших тестов из терминала.

Запустить:

HEADLESS="false" SLOWMO=100 npm run test

Отдельное выполнение одиночного теста

Иногда вам нужно запустить один тест отдельно от других, чтобы найти ошибки. Для этого воспользуемся jest-cli, который мы установили в самом начале. А теперь вернемся к тесту на перехват запроса, потому что мы не смогли его увидеть.

Запустить:

HEADLESS="false" SLOWMO=100 jest -t 'Intercept Request'

Но все же это было довольно быстро, не так ли? Давайте исправим это с помощью jestPuppeteer.debug().

jestPuppeteer.debug ()

JestPuppeteer.debug () приостанавливает выполнение теста и дает вам время увидеть, что происходит в браузере. Чтобы продолжить выполнение, вам просто нужно нажать Enter. Итак, теперь вы можете просто раскомментировать строку 11 кода для перехвата запросов и запустить предыдущую команду. И вы четко увидите, что изображение на главной странице не отображается, потому что запрос на него был перехвачен.

Бонусный регистратор кукловода

В конце я хотел бы предложить вам одно расширение для Chrome, которое может пригодиться, когда вы пишете тесты с помощью кукловода. Это Puppeteer Recorder, который позволяет вам записывать действия вашего браузера и генерировать скрипт Puppeteer.

Заключение

В этой статье мы рассмотрели два очень популярных фреймворка Jest и Puppeteer. И мы узнали, что когда мы объединяем эти два инструмента вместе, мы получаем очень мощную настройку для нашей тестовой среды. Мы многое рассмотрели, вы узнали, как интегрировать Puppeteer с Jest, как писать различные тестовые примеры, как отлаживать тесты и многое другое. Но я могу заверить, что есть еще много вещей, которые мы еще не рассмотрели, поэтому, если вам хочется, погрузитесь в официальную документацию и узнайте еще больше!

Первоначально опубликовано на touch4it.com.