Статьи

Реагируйте на сквозное тестирование и автоматизацию с помощью Detox

Detox — это комплексная среда тестирования и автоматизации, которая работает на устройстве или симуляторе, как настоящий конечный пользователь.

Разработка программного обеспечения требует быстрого реагирования на потребности пользователей и / или рынка. Этот быстрый цикл разработки может привести (рано или поздно) к тому, что части проекта будут нарушены, особенно когда проект становится настолько большим. Разработчики перегружены всеми техническими сложностями проекта, и даже деловым людям становится трудно отслеживать все сценарии, для которых предназначен продукт.

В этом сценарии необходимо, чтобы программное обеспечение оставалось на вершине проекта и позволяло нам уверенно осуществлять развертывание. Но почему комплексное тестирование? Разве недостаточно модульного и интеграционного тестирования? И зачем беспокоиться о сложности комплексного тестирования?

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

Хотите узнать React Native с нуля? Эта статья является выдержкой из нашей Премиум библиотеки. Получите полную коллекцию книг React Native, охватывающую основы, проекты, советы и инструменты и многое другое с SitePoint Premium. Присоединяйтесь сейчас всего за $ 9 / месяц .

Сквозная интеграция и модульное тестирование

Сквозное тестирование в сравнении с интеграционным тестированием или модульным тестированием: я всегда нахожу слово «против», которое заставляет людей отправляться в лагеря — как будто это война между добром и злом. Это заставляет нас отправляться в лагеря вместо того, чтобы учиться друг у друга и понимать, почему, а не как. Примеров бесчисленное множество: Angular против React, React против Angular против Vue и, более того, React против Angular против Vue против Svelte. Каждый мусорный лагерь говорит о другом.

jQuery сделал меня лучшим разработчиком, воспользовавшись шаблоном фасада $('') чтобы приручить дикого зверя DOM и сосредоточиться на выполнении поставленной задачи. Angular сделал меня лучшим разработчиком, воспользовавшись преимуществом разбиения повторно используемых частей на директивы, которые можно составить (v1). React сделал меня лучшим разработчиком благодаря использованию функционального программирования, неизменности, сравнения эталонных идентификаторов и уровня компоновки, которого я не нахожу в других средах. Vue сделал меня лучшим разработчиком благодаря использованию реактивного программирования и модели push. Я мог бы продолжать и продолжать, но я просто пытаюсь продемонстрировать, что нам нужно больше сконцентрироваться на том, почему: почему этот инструмент был создан в первую очередь, какие проблемы он решает, и есть ли другие способы решения те же проблемы.

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

График сквозного тестирования, демонстрирующий преимущества сквозного тестирования и уверенность, которую оно приносит

По мере того, как вы будете больше изучать спектр моделирования пользовательского пути, вам придется проделать большую работу по моделированию взаимодействия пользователя с продуктом. Но с другой стороны, вы получаете наибольшую уверенность, потому что вы тестируете реальный продукт, с которым взаимодействует пользователь. Итак, вы улавливаете все проблемы — будь то проблема стиля, которая может привести к тому, что целый раздел или весь процесс взаимодействия будет невидимым или неинтерактивным, проблема с контентом, проблема пользовательского интерфейса, проблема с API, проблема с сервером или база данных. вопрос. Вы получаете все это, что дает вам наибольшую уверенность.

Почему Детокс?

Мы обсудили преимущества сквозного тестирования для начала и его ценность в обеспечении максимальной уверенности при развертывании новых функций или устранении проблем. Но почему именно Детокс? На момент написания статьи это самая популярная библиотека для комплексного тестирования в React Native и самая активная в ней. Кроме того, это то, что React Native рекомендует в своей документации .

Философия тестирования Detox — это «тестирование в сером поле». Тестирование «серого ящика» — это тестирование, при котором фреймворк знает о внутренностях тестируемого продукта. Другими словами, он знает, что он находится в React Native, и знает, как запустить приложение как дочерний процесс процесса Detox и как его перезагрузить, если необходимо после каждого теста. Таким образом, каждый результат теста не зависит от других.

Предпосылки

  1. macOS High Sierra 10.13 или выше
  2. Xcode 10.1 или выше
  3. Homebrew:

      /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 
  4. Узел 8.3.0 или выше:

      brew update && brew install node 
  5. Утилиты Apple Simulator: brew tap wix/brew и brew install applesimutils

  6. Детокс CLI 10.0.7 или выше:

      npm install -g detox-cli 

Посмотреть результат в действии

Сначала давайте клонируем очень интересный проект React Native с открытым исходным кодом для обучения, затем добавим к нему Detox:

 git clone https://github.com/ahmedam55/movie-swiper-detox-testing.git cd movie-swiper-detox-testing npm install react-native run-ios 

Создайте учетную запись на веб-сайте Movie DB, чтобы иметь возможность протестировать все сценарии приложений. Затем добавьте ваше имя пользователя и пароль в файл .env с usernamePlaceholder и passwordPlaceholder соответственно:

 isTesting=true username=usernamePlaceholder password=passwordPlaceholder 

После этого вы можете запустить тесты:

 detox test 

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

  1. Перейдите полностью на последний проект React Native.
  2. Обновите все библиотеки, чтобы устранить проблемы, с которыми сталкиваются Detox при тестировании.
  3. Переключайте анимацию и бесконечные таймеры, если среда тестирует.
  4. Добавьте пакет тестов.

Настройка для новых проектов

Добавьте Detox к нашим зависимостям

Перейдите в корневой каталог вашего проекта и добавьте Detox:

 npm install detox --save-dev 

Настроить Детокс

Откройте файл package.json и добавьте следующее сразу после имени проекта config. Обязательно замените movieSwiper в конфигурации iOS на имя вашего приложения. Здесь мы сообщаем Detox, где найти бинарное приложение и команду для его сборки. (Это необязательно. Вместо этого мы всегда можем выполнить react-native run-ios .) Также выберите тип симулятора: ios.simulator , ios.none , android.emulator или android.attached . И выберите устройство для тестирования:

 { "name": "movie-swiper-detox-testing", // add these: "detox": { "configurations": { "ios.sim.debug": { "binaryPath": "ios/build/movieSwiper/Build/Products/Debug-iphonesimulator/movieSwiper.app", "build": "xcodebuild -project ios/movieSwiper.xcodeproj -scheme movieSwiper -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", "type": "ios.simulator", "name": "iPhone 7 Plus" } } } } 

Вот разбивка того, что делает конфиг выше:

  • Выполните react-native run-ios чтобы создать двоичное приложение.
  • Найдите бинарное приложение в корне проекта: find . -name "*.app" find . -name "*.app" .
  • Поместите результат в каталог build .

Перед запуском набора тестов убедитесь, что указанное вами name устройства доступно (например, iPhone 7). Вы можете сделать это из терминала, выполнив следующее:

 xcrun simctl list 

Вот как это выглядит:

Устройство-лист

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

Детокс поддерживает как Jest, так и Mocha . Мы пойдем с Jest, так как у него больше сообщества и больше возможностей. Кроме того, он поддерживает параллельное выполнение тестов, что может быть полезно для ускорения сквозных тестов по мере их увеличения.

Добавление Jest к Dev Dependencies

Выполните следующее, чтобы установить Jest:

 npm install jest jest-cli --save-dev 

Генерация тестовых файлов

Чтобы инициализировать Detox для использования Jest, выполните следующее:

 detox init -r jest 

Это создаст папку e2e в корне проекта и следующее внутри него:

  • e2e / config.json содержит глобальный конфиг для тестового бегуна:

      { "setupFilesAfterEnv": ["./init.js"], "testEnvironment": "node", "reporters": ["detox/runners/jest/streamlineReporter"], "verbose": true } 
  • e2e / init.js содержит код инициализации, который выполняется перед выполнением любого из ваших тестов:

     const detox = require('detox'); const config = require('../package.json').detox; const adapter = require('detox/runners/jest/adapter'); const specReporter = require('detox/runners/jest/specReporter'); // Set the default timeout jest.setTimeout(25000); jasmine.getEnv().addReporter(adapter); // This takes care of generating status logs on a per-spec basis. By default, jest only reports at file-level. // This is strictly optional. jasmine.getEnv().addReporter(specReporter); beforeAll(async () => { await detox.init(config); }); beforeEach(async () => { await adapter.beforeEach(); }); afterAll(async () => { await adapter.afterAll(); await detox.cleanup(); }); 
  • e2e / firstTest.spec.js — это тестовый файл Detox по умолчанию. Здесь мы разместим все тесты для приложения. Мы подробно поговорим об describe и it блоках, а также о тестовых наборах, которые мы собираемся создать позже.

Наконец, мы запускаем тесты

Чтобы запустить тесты, перейдите в корневой каталог вашего проекта и выполните следующее:

 detox test 

Поздравляем! У нас есть все для написания наших потрясающих тестов. Вы можете создавать столько e2e/*spec.js и управлять ими, сколько захотите, и тестовый исполнитель выполнит их один за другим. Файл спецификации представляет собой независимый набор функций, которые вы хотите протестировать. Например, оформление заказа, проверка гостя, аутентификация пользователя или регистрация.

Внутри spec-файла у вас будет describe . Это содержит самые маленькие блоки тестирования — it блок — который создан для чтения. Например: it should reject creating an account if name already exits . И внутри этого блока вы добавляете утверждения, необходимые, чтобы убедиться, что это правда. В идеале, мы должны перезагрузить React Native после каждого блока. Это до тех пор, пока они не зависят друг от друга. Это предотвращает ложные срабатывания и облегчает отладку. Зная, что этот тест провалился на чистом листе, вам не нужно беспокоиться обо всех других сценариях.

Погружение в наш набор тестов

Мы проверим, обслуживает ли приложение следующие сценарии.

  • Это должно запретить вход в систему с неправильными учетными данными . Это кажется очевидным, но оно критически важно для рабочего процесса приложения, поэтому его необходимо проверять при каждом изменении и / или развертывании.
  • Он должен аутентифицировать пользователей с действительными учетными данными, проверяя, что функциональность аутентификации работает должным образом.
  • Он должен выгнать пользователей, когда они выходят из системы, — проверять, действительно ли при выходе из системы пользователи выходят из экранов «Обзор», «Обзор» и «Библиотека».
  • Это должно позволить гостям просматривать только экран . Пользователи могут войти или продолжить как гости, и в этом случае они смогут получить доступ только к экрану «Обзор» и имеющимся функциям.
  • Он должен извлекать фильмы, которые соответствуют запросу — тестирование, если показанные фильмы соответствуют поисковому запросу.
  • Следует добавить в избранное — протестировать функцию добавления в избранные фильмы и убедиться, что добавленный фильм появляется в списке избранных фильмов.
  • Следует добавить в список просмотра — аналогично тестированию добавления в избранные фильмы, но для функциональности списка просмотра.
  • Он должен показывать все, когда нажимается больше — проверка функциональности дополнительных кнопок в разделах «Обзор»:
    • Тенденции Ежедневно
    • Trending Weekly
    • Популярный
    • Самые популярные
    • Убедитесь, что он переходит к просмотру списка фильмов со всеми фильмами, которые соответствуют выбранным критериям.

Ходить по коду набора тестов

Теперь пришло время пересмотреть код для тестирования приложения. Однако прежде чем сделать это, я рекомендую сначала запустить приложение на своем устройстве или симуляторе. Это для ознакомления с различными экранами и компонентами пользовательского интерфейса в приложении.

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

  • loginWithWrongCredentials()
  • loginWithRightCredentials()
  • goToLibrary()
  • signOut()
  • searchForMovie(title)

API Detox должен легко иметь смысл, даже если вы ранее не использовали его. Вот код:

 // e2e/firstTestSuite.spec.js // fetch the username and password from the .env file const username = process.env.username; const password = process.env.password; const sleep = duration => new Promise(resolve => setTimeout(() => resolve(), duration)); // function for pausing the execution of the test. Mainly used for waiting for a specific UI component to appear on the screen const loginWith = async (username, password) => { try { // click on login btn to navigate to the username, password screen const navigateToLoginBtn = await element(by.id("navigate-login-btn")); await navigateToLoginBtn.tap(); const usernameInput = await element(by.id("username-input")); const passwordInput = await element(by.id("password-input")); await usernameInput.tap(); await usernameInput.typeText(username); await passwordInput.typeText(password); const loginBtn = await element(by.id("login-btn")); await loginBtn.tap(); // to close the keyboard await loginBtn.tap(); // to start the authentication process const errorMessage = await element( by.text("Invalid username and/or password") ); return { errorMessage, usernameInput, passwordInput }; } catch (e) { console.log( "A sign out has not been done, which made the `navigate-login-btn` not found" ); } }; const loginWithWrongCredentials = async () => await loginWith("alex339", "9sdfhsakjf"); // log in with some random incorrect credentials const loginWithRightCredentials = async () => await loginWith(username, password); // log in with the correct credentials const goToLibrary = async () => { const libraryBtn = await element(by.id("navigation-btn-Library")); await libraryBtn.tap(); }; const goToExplore = async () => { const exploreBtn = await element(by.id("navigation-btn-Explore")); await exploreBtn.tap(); }; const signOut = async () => { await goToLibrary(); const settingsBtn = await element(by.id("settings-btn")); await settingsBtn.tap(); const signOutBtn = await element(by.id("sign-out-btn")); await signOutBtn.tap(); }; const continueAsGuest = async () => { const continueAsGuestBtn = await element(by.id("continue-as-guest")); await continueAsGuestBtn.tap(); }; const searchForMovie = async movieTitle => { const searchMoviesInput = await element(by.id("search-input-input")); await searchMoviesInput.tap(); await searchMoviesInput.clearText(); await searchMoviesInput.typeText(movieTitle); }; const goBack = async () => { const goBackBtn = await element(by.id("go-back-btn")); goBackBtn.tap(); }; const goToWatchListMovies = async () => { const watchListBtn = await element(by.id("my-watchlist")); await watchListBtn.tap(); }; const goToFavoriteMovies = async () => { const favoriteMoviesBtn = await element(by.id("my-favorite-movies")); await favoriteMoviesBtn.tap(); }; const clickFavoriteButton = async () => { const addToWatchListBtn = await element(by.id("add-to-favorite-btn")); await addToWatchListBtn.tap(); }; const clickWatchListButton = async () => { const addToWatchListBtn = await element(by.id("add-to-watch-list-btn")); await addToWatchListBtn.tap(); }; const removeTestMoviesFromLists = async () => { try { await loginWithRightCredentials(); await goToLibrary(); await goToWatchListMovies(); const movieItemInWatchList = await element( by.text("Crazy Rich Asians").withAncestor(by.id("watch-list")) ); await movieItemInWatchList.tap(); await clickWatchListButton(); await goToLibrary(); await goToFavoriteMovies(); const movieItemInFavorites = await element( by.text("Avengers: Endgame").withAncestor(by.id("favorite-list")) ); await movieItemInFavorites.tap(); await clickFavoriteButton(); } catch (e) {} await signOut(); }; // next: add function for asserting movie items 

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

 const assertMovieItems = async (moviesTitles = []) => { for (let i = 0; i < moviesTitles.length; i++) { const moviesItem = await element(by.text(moviesTitles[i])); await expect(moviesItem).toBeVisible(); } }; // next: create the test suite 

На данный момент мы готовы к созданию набора тестов. Это должно быть заключено в блок describe . Чтобы у каждого теста была «чистая» отправная точка, мы используем следующие методы жизненного цикла:

  • beforeAll : выполняется до beforeAll этого набора тестов. В этом случае мы вызываем removeTestMoviesFromLists() . Как вы видели ранее, это эквивалент последовательности проверки при запуске, когда пользователь входит в систему и посещает различные страницы и нажимает на различные кнопки, которые будут использоваться в тестах. Это гарантирует, что приложение находится в минимальном функциональном состоянии, прежде чем оно начнет выполнять тесты.
  • beforeEach : выполняется перед каждым тестом в этом наборе тестов. В этом случае мы хотим перезагрузить React Native. Обратите внимание, что это имеет тот же эффект, что и нажатие клавиш + r , rr или Ctrl + r на клавиатуре.
  • afterEach : выполняется после каждого теста в этом наборе тестов. В этом случае мы хотим вывести пользователя из системы, что означает, что в каждом из наших тестов мы должны снова регистрировать пользователя. Опять же, это хорошая практика, которую нужно использовать при написании тестов: каждый тест должен иметь та же отправная точка. Это гарантирует, что они могут работать в любом порядке и по-прежнему давать те же результаты:
     describe("Project Test Suite", () => { beforeAll(async () => { await removeTestMoviesFromLists(); }); beforeEach(async () => { await device.reloadReactNative(); }); afterEach(async () => { try { await signOut(); } catch (e) {} }); // next: run the individual tests }); 

Теперь давайте пройдемся по отдельным тестам. Они могут быть определены внутри блока it . Каждый it блок начинается с чистого листа и отстаивает определенный, четко определенный сценарий (который мы рассмотрели в предыдущем разделе). Каждый тест имеет предсказуемый результат, который мы должны утверждать:

 it("should disallow login with wrong credentials", async () => { const { errorMessage, usernameInput, passwordInput } = await loginWithWrongCredentials(); await expect(errorMessage).toBeVisible(); await expect(usernameInput).toBeVisible(); await expect(passwordInput).toBeVisible(); }); it("should login with right credentials", async () => { await loginWithRightCredentials(); await goToLibrary(); const watchListBtn = element(by.id("my-watchlist")); const favoriteMoviesBtn = element(by.id("my-favorite-movies")); await expect(watchListBtn).toBeVisible(); await expect(favoriteMoviesBtn).toBeVisible(); }); it("should kick user out when sign out is clicked", async () => { await loginWithRightCredentials(); await goToLibrary(); await signOut(); const loginBtn = await element(by.id("navigate-login-btn")); await expect(loginBtn).toBeVisible(); }); it("should allow guest in for Browse only", async () => { await continueAsGuest(); await goToLibrary(); const watchListBtn = element(by.id("my-watchlist")); const favoriteMoviesBtn = element(by.id("my-favorite-movies")); await expect(watchListBtn).toBeNotVisible(); await expect(favoriteMoviesBtn).toBeNotVisible(); await goToExplore(); const moviesSwipingView = element(by.id("movies-swiping-view")); await expect(moviesSwipingView).toBeNotVisible(); }); it("should fetch and render the searches properly", async () => { await loginWithRightCredentials(); const searches = [ { query: "xmen", results: ["X-Men: Apocalypse", "X-Men: Days of Future Past"] }, { query: "avengers", results: ["Avengers: Endgame", "Avengers: Age of Ultron"] }, { query: "wolverine", results: ["Logan", "The Wolverine"] } ]; for (let i = 0; i < searches.length; i++) { const currentSearch = searches[i]; await searchForMovie(currentSearch.query); await assertMovieItems(currentSearch.results); } }); it("should add to favorite", async () => { await loginWithRightCredentials(); await searchForMovie("avengers"); await element(by.text("Avengers: Endgame")).tap(); await clickFavoriteButton(); await goBack(); await goToLibrary(); await goToFavoriteMovies(); await sleep(3000); var movieItemInFavorites = await element( by.id("favorite-list").withDescendant(by.text("Avengers: Endgame")) ); await expect(movieItemInFavorites).toBeVisible(); }); it("should add to watchlist", async () => { await loginWithRightCredentials(); await searchForMovie("crazy rich"); await element(by.text("Crazy Rich Asians")).tap(); await clickWatchListButton(); await goBack(); await goToLibrary(); await goToWatchListMovies(); await sleep(3000); const movieItemInFavorites = await element( by.id("watch-list").withDescendant(by.text("Crazy Rich Asians")) ); await expect(movieItemInFavorites).toBeVisible(); }); it("should show all lists more is clicked", async () => { await loginWithRightCredentials(); const trendingDailyMoreBtn = await element(by.id("trending-daily-more")); await trendingDailyMoreBtn.tap(); await goBack(); await sleep(300); const trendingWeeklyMoreBtn = await element(by.id("trending-weekly-more")); await trendingWeeklyMoreBtn.tap(); await goBack(); await sleep(300); const popularMoreBtn = await element(by.id("popular-more")); await popularMoreBtn.tap(); await goBack(); await sleep(300); const browseSectionsView = await element(by.id("browse-sections-view")); await browseSectionsView.scrollTo("bottom"); const topRatedMoreBtn = await element(by.id("top-rated-more")); await topRatedMoreBtn.tap(); }); 

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

  1. Инициализировать состояние . Здесь мы регистрируем пользователя, поэтому у каждого теста одна и та же отправная точка.
  2. Выберите компонент пользовательского интерфейса . Здесь мы используем средства сопоставления для определения конкретных компонентов пользовательского интерфейса.
  3. Запустить действие . Здесь мы запускаем действие с выбранным нами компонентом пользовательского интерфейса.
  4. Утвердите, что ожидаемый результат существует или не существует . Именно здесь мы используем expect() чтобы проверить, вызвало ли действие другой компонент пользовательского интерфейса, чтобы показать его или скрыть с экрана. Если утверждение возвращает true , тест пройден.

Примечание: из-за постоянно меняющейся природы приложения элементы фильма, которые мы утверждаем, могут меняться очень часто. Если вы читаете это через некоторое время после публикации этой статьи, сначала убедитесь, что вы вручную проверили, видны ли определенные элементы на экране. Это поможет избежать ненужного сбоя теста и избавит вас от головной боли при запуске демонстрации.

Matchers

Вы можете сопоставить или выбрать любой элемент пользовательского интерфейса по идентификатору, тексту, метке, родителю, потомку (на любом уровне) или признакам. Вот пара примеров:

 const usernameInput = await element(by.id("username-input")); 
 const errorMessage = await element(by.text("Invalid username and/or password")); 

Действия, которые будут выполнены

Детокс может выполнять огромный набор действий над элементами пользовательского интерфейса: tap , longPress , multiTap , tapAtPoint , swipe , typeText , clearText , scroll , scrollTo и другие .

Вот несколько примеров:

 await usernameInput.tap(); await usernameInput.typeText(username); await passwordInput.clearText(); const browseSectionsView = await element(by.id("browse-sections-view")); await browseSectionsView.scrollTo("bottom"); 

Утверждения для тестирования

Детокс имеет набор утверждений, которые могут быть выполнены для соответствующих элементов пользовательского интерфейса: toBeVisible , toNotBeVisible , toExist , toNotExist , toHaveText , toHaveLabel , toHaveId , toHaveValue . Вот пара примеров:

 const assertMovieItems = async (moviesTitles = []) => { for (let i = 0; i < moviesTitles.length; i++) { const moviesItem = await element(by.text(moviesTitles[i])); await expect(moviesItem).toBeVisible(); } }; await assertMovieItems(["Avengers: Endgame", "Avengers: Age of Ultron"]); 
 const watchListBtn = element(by.id("my-watchlist")); await expect(watchListBtn).toBeNotVisible(); 

Проблемы и Рецепты

Бесконечные циклические анимации или таймеры

Одна из проблем, с которыми я столкнулся, заключается в том, что Detox останавливается, если есть цикл по таймеру или анимация, которая никогда не заканчивается. Мне пришлось сделать следующее для устранения таких проблем:

  1. Поиск и отладка деталей в дереве приложений и импорт путем их изменения и устранения.
  2. Снова запустите набор тестов, чтобы проверить, сохраняется ли проблема.
  3. После этого и в большинстве случаев проблема заключается в анимации, которая начинается сразу после ее завершения. Поэтому я импортировал react-native-config , который является очень удобным инструментом для установки некоторых переменных среды для переключения некоторых режимов или функций в зависимости от среды. В моем случае это было добавление isTesting=true в файл .env , проверка его в кодовой базе и отключение цикла анимации или уменьшение длительности, что ускоряет набор тестов.

Как вы можете видеть, это в основном вопрос игры с настройками анимации в вашем приложении. Для получения дополнительной информации об устранении неполадок Detox, вы можете проверить следующую документацию:

Добавление TestID к правильному элементу пользовательского интерфейса

Еще одной проблемой является testID компонента для передачи testID , поскольку Detox не поддерживает его для пользовательских компонентов. Иногда вам нужно обернуть компонент встроенным компонентом, например компонентом View , чтобы сопоставить его и взаимодействовать с ним. Это особенно верно, если код внутреннего встроенного компонента является импортированной библиотекой в ​​папке node_modules .

Составьте TestID с контекстными данными

Другой сценарий, с которым мне пришлось столкнуться, — это компоненты, которые отображаются в нескольких местах с разными обработчиками событий и заголовками. Поэтому мне пришлось создать составной testID с заголовком, опущенным регистром и дефисом и идентификатором testID для компонента.

Например, кнопка more всех разделов просмотра: поскольку для каждого из них отображается один и тот же компонент:

  const testID = `${(this.props.title||'').toLowerCase().replace(/\s/g, '-')}-more` return ( ... <AppButton onlyText style={styles.moreButton} textStyle={styles.moreButtonText} onPress={this.onMorePress} testID={testID} > MORE </AppButton> } 

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

Сужение селекторов

Поскольку некоторые навигаторы стремятся сохранить предыдущие экраны в дереве, Детокс найдет два элемента с одинаковым идентификатором (текст, идентификатор, метка) и выдаст исключение. Таким образом, нам нужно отфильтровать элементы с определенного экрана, чтобы получить то, что нам нужно. Вы можете сделать это, используя метод withAncestor() , который соответствует определенному идентификатору предка:

 const movieItemInWatchList = await element( by.text("Crazy Rich Asians").withAncestor(by.id("watch-list")) ); await movieItemInWatchList.tap(); 

Давайте посмотрим на результат более интересным способом

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

Для имитации ввода текста клавиатура должна появляться при выборе ввода. Чтобы включить это, перейдите в Simulator> Keyboard> Toggle Software Keyboard . Вы должны сделать этот шаг перед запуском тестов.

Вывод

В этом руководстве вы узнали, как использовать Detox для реализации сквозного тестирования в приложении React Native.

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

В этом руководстве мы тестировали только iOS, но вы также сможете запускать тесты на Android. Обратите внимание, что вам, возможно, придется понизить ваше приложение до более ранней версии React Native и Detox, чтобы оно работало на Android. Это потому, что поддержка iOS лучше в Detox.

Вы можете просмотреть исходный код этого репозитория GitHub .