1--- 2title: Run E2E tests on EAS Build 3sidebar_title: Run E2E tests 4description: Learn how to set up and run E2E tests on EAS Build with popular libraries such as Detox. 5--- 6 7import { Collapsible } from '~/ui/components/Collapsible'; 8import { Terminal, DiffBlock } from '~/ui/components/Snippet'; 9import ImageSpotlight from '~/components/plugins/ImageSpotlight'; 10 11> **Warning** EAS Build support for E2E testing is in a _very early_ state. The intention of this guide is to explain how you can run E2E tests on the service today, 12> without all of the affordances that we plan to build in the future. This guide will evolve over time as support for testing workflows in EAS Build improves. 13 14With EAS Build, you can build a workflow for running E2E tests for your application. In this guide, you will learn how to use one of the most popular libraries ([Detox](https://wix.github.io/Detox)) to do that. 15 16This guide explains how to run E2E tests with Detox in a bare workflow project. You can use [`@config-plugins/detox`](https://github.com/expo/config-plugins/tree/main/packages/detox) for a managed project, but you may need to adjust some of the instructions in this guide to do so. 17 18## Running iOS tests 19 20### 1. Initialize a new bare workflow project 21 22Let's start by initializing a new Expo project, installing and configuring `@config-plugins/detox`, and running `npx expo prebuild` to generate the native projects. 23 24Start with the following commands: 25 26<Terminal 27 cmd={[ 28 '# Initialize a new project', 29 '$ npx create-expo-app eas-tests-example', 30 '# cd into the project directory', 31 '$ cd eas-tests-example', 32 '# Install @config-plugins/detox', 33 '$ npm install --save-dev @config-plugins/detox', 34 ]} 35/> 36 37Now, open **app.json** and add the `@config-plugins/detox` plugin to your `plugins` list (this must be done before prebuilding). This will automatically configure the Android native code to support Detox. 38 39```json app.json 40{ 41 "expo": { 42 // ... 43 "plugins": ["@config-plugins/detox"] 44 } 45} 46``` 47 48Run prebuild to generate the native projects: 49 50<Terminal cmd={['$ npx expo prebuild']} /> 51 52### 2. Make home screen interactive 53 54The first step to writing E2E tests is to have something to test - we have an empty app, so let's make our app interactive. We can add a button and display some new text when it's pressed. 55Later, we're going to write a test that's going to tap the button and check whether the text has been displayed. 56 57<div style={{ display: 'flex', justifyContent: 'center' }}> 58 <img src="/static/images/eas-build/tests/01-click-me.png" style={{ maxWidth: '45%' }} /> 59 <img src="/static/images/eas-build/tests/02-hi.png" style={{ maxWidth: '45%' }} /> 60</div> 61 62<Collapsible summary=" See the source code"> 63 64```js App.js 65import { StatusBar } from 'expo-status-bar'; 66import { useState } from 'react'; 67import { Pressable, StyleSheet, Text, View } from 'react-native'; 68 69export default function App() { 70 const [clicked, setClicked] = useState(false); 71 72 return ( 73 <View style={styles.container}> 74 {!clicked && ( 75 <Pressable testID="click-me-button" style={styles.button} onPress={() => setClicked(true)}> 76 <Text style={styles.text}>Click me</Text> 77 </Pressable> 78 )} 79 {clicked && <Text style={styles.hi}>Hi!</Text>} 80 <StatusBar style="auto" /> 81 </View> 82 ); 83} 84 85const styles = StyleSheet.create({ 86 container: { 87 flex: 1, 88 backgroundColor: '#fff', 89 alignItems: 'center', 90 justifyContent: 'center', 91 }, 92 hi: { 93 fontSize: 30, 94 color: '#4630EB', 95 }, 96 button: { 97 alignItems: 'center', 98 justifyContent: 'center', 99 paddingVertical: 12, 100 paddingHorizontal: 32, 101 borderRadius: 4, 102 elevation: 3, 103 backgroundColor: '#4630EB', 104 }, 105 text: { 106 fontSize: 16, 107 lineHeight: 21, 108 fontWeight: 'bold', 109 letterSpacing: 0.25, 110 color: 'white', 111 }, 112}); 113``` 114 115</Collapsible> 116 117### 3. Set up Detox 118 119#### Install dependencies 120 121Let's add two development dependencies to the project - `jest` and `detox`. `jest` (or `mocha`) is required because `detox` does not have its own test-runner. 122 123<Terminal 124 cmd={[ 125 '# Install jest and detox', 126 '$ npm install --save-dev jest detox', 127 '# Create Detox configuration files', 128 '$ npx detox init -r jest', 129 ]} 130/> 131 132> See the official Detox docs at https://wix.github.io/Detox/docs/introduction/getting-started/ and https://wix.github.io/Detox/docs/guide/jest to learn about any potential updates to this process. 133 134#### Configure Detox 135 136Detox requires you to specify both the build command and path to the binary produced by it. Technically, the build command is not necessary when running tests on EAS Build, but allows you to run tests locally (for example, using `npx detox build --configuration ios.release`). 137 138Edit **detox.config.js** and replace the configuration with: 139 140```js detox.config.js 141/** @type {Detox.DetoxConfig} */ 142module.exports = { 143 logger: { 144 level: process.env.CI ? 'debug' : undefined, 145 }, 146 testRunner: { 147 $0: 'jest', 148 args: { 149 config: 'e2e/jest.config.js', 150 _: ['e2e'], 151 }, 152 }, 153 artifacts: { 154 plugins: { 155 log: process.env.CI ? 'failing' : undefined, 156 /* @info In Detox 20, this plugin setting will take screenshots if tests fail */ 157 screenshot: 'failing', 158 /* @end */ 159 }, 160 }, 161 apps: { 162 'ios.release': { 163 type: 'ios.app', 164 /* @info This is project-specific, replace eastestsexample with correct value */ 165 build: 166 'xcodebuild -workspace ios/eastestsexample.xcworkspace -scheme eastestsexample -configuration Release -sdk iphonesimulator -arch x86_64 -derivedDataPath ios/build', 167 binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/eastestsexample.app', 168 /* @end */ 169 }, 170 'android.release': { 171 type: 'android.apk', 172 build: 173 'cd android && ./gradlew :app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release && cd ..', 174 binaryPath: 'android/app/build/outputs/apk/release/app-release.apk', 175 }, 176 }, 177 devices: { 178 simulator: { 179 type: 'ios.simulator', 180 device: { 181 type: 'iPhone 14', 182 }, 183 }, 184 emulator: { 185 type: 'android.emulator', 186 device: { 187 avdName: 'pixel_4', 188 }, 189 }, 190 }, 191 configurations: { 192 'ios.release': { 193 device: 'simulator', 194 app: 'ios.release', 195 }, 196 'android.release': { 197 device: 'emulator', 198 app: 'android.release', 199 }, 200 }, 201}; 202``` 203 204### 4. Write E2E tests 205 206Next, we'll add our first E2E tests. Delete the auto-generated **e2e/firstTest.e2e.js** and create our own **e2e/homeScreen.e2e.js** with the following contents: 207 208```js e2e/homeScreen.e2e.js 209describe('Home screen', () => { 210 beforeAll(async () => { 211 await device.launchApp(); 212 }); 213 214 beforeEach(async () => { 215 await device.reloadReactNative(); 216 }); 217 218 it('"Click me" button should be visible', async () => { 219 await expect(element(by.id('click-me-button'))).toBeVisible(); 220 }); 221 222 it('shows "Hi!" after tapping "Click me"', async () => { 223 await element(by.id('click-me-button')).tap(); 224 await expect(element(by.text('Hi!'))).toBeVisible(); 225 }); 226}); 227``` 228 229There are two tests in the suite: 230 231- One that checks whether the "Click me" button is visible on the home screen. 232- Another that verifies that tapping the button triggers displaying "Hi!". 233 234Both tests assume the button has the `testID` set to `click-me-button`. See [the source code](#2-make-home-screen-interactive) for details. 235 236### 5. Configure EAS Build 237 238Now that we have configured Detox and written our first E2E test, let's configure EAS Build and run the tests in the cloud. 239 240#### Create eas.json 241 242The following command creates [eas.json](/build/eas-json.mdx) in the project's root directory: 243 244<Terminal cmd={['$ eas build:configure']} /> 245 246#### Configure EAS Build 247 248There are a few more steps to configure EAS Build for running E2E tests as part of the build: 249 250- Android tests: 251 - Tests are run in the Android Emulator. You will define a build profile that builds your app for the emulator (produces an `apk` file). 252 - Install the emulator and all its system dependencies. 253- iOS test: 254 - Tests are run in the iOS Simulator. You will define a build profile that builds your app for the simulator. 255 - Install the [`applesimutils`](https://github.com/wix/AppleSimulatorUtils) command line util. 256- Configure EAS Build to run Detox tests after successfully building the app. 257 258Edit **eas.json** and add the `test` build profile: 259 260```json eas.json 261{ 262 "build": { 263 "test": { 264 "android": { 265 "gradleCommand": ":app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release", 266 "withoutCredentials": true 267 }, 268 "ios": { 269 "simulator": true 270 } 271 } 272 } 273} 274``` 275 276Create **eas-hooks/eas-build-pre-install.sh** that installs the necessary tools and dependencies for the given platform: 277 278```sh eas-hooks/eas-build-pre-install.sh 279#!/usr/bin/env bash 280 281set -eox pipefail 282 283if [[ "$EAS_BUILD_RUNNER" == "eas-build" && "$EAS_BUILD_PROFILE" == "test"* ]]; then 284 if [[ "$EAS_BUILD_PLATFORM" == "android" ]]; then 285 sudo apt-get --quiet update --yes 286 287 # Install emulator & video bridge dependencies 288 # Source: https://github.com/react-native-community/docker-android/blob/master/Dockerfile 289 sudo apt-get --quiet install --yes \ 290 libc6 \ 291 libdbus-1-3 \ 292 libfontconfig1 \ 293 libgcc1 \ 294 libpulse0 \ 295 libtinfo5 \ 296 libx11-6 \ 297 libxcb1 \ 298 libxdamage1 \ 299 libnss3 \ 300 libxcomposite1 \ 301 libxcursor1 \ 302 libxi6 \ 303 libxext6 \ 304 libxfixes3 \ 305 zlib1g \ 306 libgl1 \ 307 pulseaudio \ 308 socat 309 310 # Emulator must be API 31 -- API 32 and 33 fail due to https://github.com/wix/Detox/issues/3762 311 sdkmanager --install "system-images;android-31;google_apis;x86_64" 312 avdmanager --verbose create avd --force --name "pixel_4" --device "pixel_4" --package "system-images;android-31;google_apis;x86_64" 313 else 314 brew tap wix/brew 315 brew install applesimutils 316 fi 317fi 318 319``` 320 321Next, create **eas-hooks/eas-build-on-success.sh** with the following contents. The script runs different commands for Android and iOS. For iOS, the only command is `detox test`. For Android, it's a bit more complicated. You'll have to start the emulator before running the tests as `detox` sometimes seems to be having problems with starting the emulator on its own and it can get stuck on running the first test from your test suite. After the `detox test` run, there is a command that kills the previously started emulator. 322 323```sh eas-hooks/eas-build-on-success.sh 324#!/usr/bin/env bash 325 326function cleanup() 327{ 328 echo 'Cleaning up...' 329 if [[ "$EAS_BUILD_PLATFORM" == "android" ]]; then 330 # Kill emulator 331 adb emu kill & 332 fi 333} 334 335if [[ "$EAS_BUILD_PROFILE" != "test" ]]; then 336 exit 337fi 338 339# Fail if anything errors 340set -eox pipefail 341# If this script exits, trap it first and clean up the emulator 342trap cleanup EXIT 343 344ANDROID_EMULATOR=pixel_4 345 346if [[ "$EAS_BUILD_PLATFORM" == "android" ]]; then 347 # Start emulator 348 $ANDROID_SDK_ROOT/emulator/emulator @$ANDROID_EMULATOR -no-audio -no-boot-anim -no-window -use-system-libs 2>&1 >/dev/null & 349 350 # Wait for emulator 351 max_retry=10 352 counter=0 353 until adb shell getprop sys.boot_completed; do 354 sleep 10 355 [[ counter -eq $max_retry ]] && echo "Failed to start the emulator!" && exit 1 356 counter=$((counter + 1)) 357 done 358 359 # Execute Android tests 360 if [[ "$EAS_BUILD_PROFILE" == "test" ]]; then 361 detox test --configuration android.release 362 fi 363else 364 # Execute iOS tests 365 if [[ "$EAS_BUILD_PROFILE" == "test" ]]; then 366 detox test --configuration ios.release 367 fi 368fi 369``` 370 371Edit **package.json** to use [EAS Build hooks](/build-reference/npm-hooks.mdx) to run the above scripts on EAS Build: 372 373```json package.json 374{ 375 "scripts": { 376 "eas-build-pre-install": "./eas-hooks/eas-build-pre-install.sh", 377 "eas-build-on-success": "./eas-hooks/eas-build-on-success.sh" 378 } 379} 380``` 381 382> Don't forget to add executable permissions to **eas-build-pre-install.sh** and **eas-build-on-success.sh**. Run `chmod +x eas-hooks/*.sh`. 383 384### 6. Run tests on EAS Build 385 386Running the tests on EAS Build is like running a regular build: 387 388<Terminal cmd={['$ eas build -p all -e test']} /> 389 390If you have set up everything correctly you should see the successful test result in the build logs: 391 392<ImageSpotlight src="/static/images/eas-build/tests/03-logs.png" style={{ maxWidth: '90%' }} /> 393 394### 7. Upload screenshots of failed test cases 395 396> This step is optional but highly recommended. 397 398When an E2E test case fails, it can be helpful to see the screenshot of the application state. EAS Build makes it easy to upload any arbitrary build artifacts using the [`buildArtifactPaths`](/build-reference/eas-json.mdx#buildartifactpaths) field in **eas.json**. 399 400#### Take screenshots for failed tests 401 402Detox supports taking in-test screenshots of the device. The [**detox.config.js** sample](/build-reference/e2e-tests/#configure-detox) above includes a line to configure Detox to take screenshots of failed tests. 403 404#### Configure EAS Build for screenshots upload 405 406Edit **eas.json** and add `buildArtifactPaths` to the `test` build profile: 407 408```json eas.json 409{ 410 "build": { 411 "test": { 412 "android": { 413 "gradleCommand": ":app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release", 414 "withoutCredentials": true 415 }, 416 "ios": { 417 "simulator": true 418 }, 419 /* @info */ 420 "buildArtifactPaths": ["artifacts/**/*.png"] 421 /* @end */ 422 } 423 } 424} 425``` 426 427In contrast to `applicationArchivePath`, the build artifacts defined at `buildArtifactPaths` will be uploaded even if the build fails. All `.png` files from the `artifacts` directory will be packed into a tarball and uploaded to AWS S3. You can download them later from the build details page. 428 429If you run E2E tests locally, remember to add `artifacts` to `.gitignore`: 430 431```stylus .gitignore 432artifacts/ 433``` 434 435#### Break a test and run a build 436 437To test the new configuration, let's break a test and see that EAS Build uploads the screenshots. 438 439Edit **e2e/homeScreen.e2e.js** and make the following change: 440 441<DiffBlock source="/static/diffs/e2e-tests-homescreen.diff" /> 442 443Run an iOS build with the following command and wait for it to finish: 444 445<Terminal cmd={['$ eas build -p ios -e test']} /> 446 447After going to the build details page you should see that the build failed. Use the **"Download artifacts"** button to download and examine the screenshot: 448 449<ImageSpotlight src="/static/images/eas-build/tests/04-artifacts.png" style={{ maxWidth: '90%' }} /> 450 451## Repository 452 453The full example from this guide is available at https://github.com/expo/eas-tests-example. 454 455## Alternative approaches 456 457### Using development builds to speed up test run time 458 459The above guide explains how to run E2E tests against a release build of your project, which requires executing a full native build before each test run. Re-building the native app each time you run E2E tests may not be desirable if only the project JavaScript or assets have changed. However, this is necessary for release builds because the app JavaScript bundle is embedded into the binary. 460 461Instead, we can use [development builds](/develop/development-builds/introduction/) to load from a local development server or from [published updates](/eas-update/introduction/) to save time and CI resources. This can be done by having your E2E test runner invoke the app with a URL that points to a specific update bundle URL, as described in the [development builds deep linking URLs guide](/develop/development-builds/development-workflows/#deep-linking-urls). 462 463Development builds typically display an onboarding welcome screen when an app is launched for the first time, which intends to provide context about the `expo-dev-client` UI for developers. However, it can interfere with your E2E tests (which expect to interact with your app and not an onboarding screen). To skip the onboarding screen in a test environment, the query parameter `disableOnboarding=1` can be appended to the project URL (an EAS Update URL or a local development server URL). 464 465An example of such a Detox test is shown below. Full example code is available on the [eas-tests-example](https://github.com/expo/eas-tests-example) repository. 466 467<Collapsible summary="e2e/homeScreen.e2e.js"> 468 469```js 470/* @info New line */ 471const { openApp } = require('./utils/openApp'); 472/* @end */ 473 474describe('Home screen', () => { 475 beforeEach(async () => { 476 /* @info New line */ await openApp(); /* @end */ 477 }); 478 479 it('"Click me" button should be visible', async () => { 480 await expect(element(by.id('click-me-button'))).toBeVisible(); 481 }); 482 483 it('shows "Hi!" after tapping "Click me"', async () => { 484 await element(by.id('click-me-button')).tap(); 485 await expect(element(by.text('Hi!'))).toBeVisible(); 486 }); 487}); 488``` 489 490</Collapsible> 491 492<Collapsible summary="e2e/utils/openApp.js (new file)"> 493 494```js 495const appConfig = require('../../../app.json'); 496const { resolveConfig } = require('detox/internals'); 497 498const platform = device.getPlatform(); 499 500module.exports.openApp = async function openApp() { 501 const config = await resolveConfig(); 502 if (config.configurationName.split('.')[1] === 'debug') { 503 return await openAppForDebugBuild(platform); 504 } else { 505 return await device.launchApp({ 506 newInstance: true, 507 }); 508 } 509}; 510 511async function openAppForDebugBuild(platform) { 512 const deepLinkUrl = process.env.EXPO_USE_UPDATES 513 ? // Testing latest published EAS update for the test_debug channel 514 getDeepLinkUrl(getLatestUpdateUrl()) 515 : // Local testing with packager 516 getDeepLinkUrl(getDevLauncherPackagerUrl(platform)); 517 518 if (platform === 'ios') { 519 await device.launchApp({ 520 newInstance: true, 521 }); 522 sleep(3000); 523 await device.openURL({ 524 url: deepLinkUrl, 525 }); 526 } else { 527 await device.launchApp({ 528 newInstance: true, 529 url: deepLinkUrl, 530 }); 531 } 532 533 await sleep(3000); 534} 535 536const getDeepLinkUrl = url => 537 `eastestsexample://expo-development-client/?url=${encodeURIComponent(url)}`; 538 539const getDevLauncherPackagerUrl = platform => 540 `http://localhost:8081/index.bundle?platform=${platform}&dev=true&minify=false&disableOnboarding=1`; 541 542const getLatestUpdateUrl = () => 543 `https://u.expo.dev/${getAppId()}?channel-name=test_debug&disableOnboarding=1`; 544 545const getAppId = () => appConfig?.expo?.extra?.eas?.projectId ?? ''; 546 547const sleep = t => new Promise(res => setTimeout(res, t)); 548``` 549 550</Collapsible> 551 552<Collapsible summary="detox.config.js"> 553 554```js 555/** @type {Detox.DetoxConfig} */ 556module.exports = { 557 logger: { 558 level: process.env.CI ? 'debug' : undefined, 559 }, 560 testRunner: { 561 $0: 'jest', 562 args: { 563 config: 'e2e/jest.config.js', 564 _: ['e2e'], 565 }, 566 }, 567 artifacts: { 568 plugins: { 569 log: process.env.CI ? 'failing' : undefined, 570 screenshot: 'failing', 571 }, 572 }, 573 apps: { 574 'ios.debug': { 575 type: 'ios.app', 576 build: 577 'xcodebuild -workspace ios/eastestsexample.xcworkspace -scheme eastestsexample -configuration Debug -sdk iphonesimulator -arch x86_64 -derivedDataPath ios/build', 578 binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/eastestsexample.app', 579 }, 580 'ios.release': { 581 type: 'ios.app', 582 build: 583 'xcodebuild -workspace ios/eastestsexample.xcworkspace -scheme eastestsexample -configuration Release -sdk iphonesimulator -arch x86_64 -derivedDataPath ios/build', 584 binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/eastestsexample.app', 585 }, 586 'android.debug': { 587 type: 'android.apk', 588 build: 589 'cd android && ./gradlew :app:assembleDebug :app:assembleAndroidTest -DtestBuildType=debug && cd ..', 590 binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', 591 }, 592 'android.release': { 593 type: 'android.apk', 594 build: 595 'cd android && ./gradlew :app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release && cd ..', 596 binaryPath: 'android/app/build/outputs/apk/release/app-release.apk', 597 }, 598 }, 599 devices: { 600 simulator: { 601 type: 'ios.simulator', 602 device: { 603 type: 'iPhone 14', 604 }, 605 }, 606 emulator: { 607 type: 'android.emulator', 608 device: { 609 avdName: 'pixel_4', 610 }, 611 }, 612 }, 613 configurations: { 614 'ios.debug': { 615 device: 'simulator', 616 app: 'ios.debug', 617 }, 618 'ios.release': { 619 device: 'simulator', 620 app: 'ios.release', 621 }, 622 'android.debug': { 623 device: 'emulator', 624 app: 'android.debug', 625 }, 626 'android.release': { 627 device: 'emulator', 628 app: 'android.release', 629 }, 630 }, 631}; 632``` 633 634</Collapsible> 635 636<Collapsible summary="eas-hooks/eas-build-on-success.sh"> 637 638```sh 639#!/usr/bin/env bash 640 641function cleanup() 642{ 643 echo 'Cleaning up...' 644 if [[ "$EAS_BUILD_PLATFORM" == "android" ]]; then 645 # Kill emulator 646 adb emu kill & 647 fi 648} 649 650if [[ "$EAS_BUILD_PROFILE" != "test"* ]]; then 651 exit 652fi 653 654# Fail if anything errors 655set -eox pipefail 656# If this script exits, trap it first and clean up the emulator 657trap cleanup EXIT 658 659ANDROID_EMULATOR=pixel_4 660 661if [[ "$EAS_BUILD_PLATFORM" == "android" ]]; then 662 # Start emulator 663 $ANDROID_SDK_ROOT/emulator/emulator @$ANDROID_EMULATOR -no-audio -no-boot-anim -no-window -use-system-libs 2>&1 >/dev/null & 664 665 # Wait for emulator 666 max_retry=10 667 counter=0 668 until adb shell getprop sys.boot_completed; do 669 sleep 10 670 [[ counter -eq $max_retry ]] && echo "Failed to start the emulator!" && exit 1 671 counter=$((counter + 1)) 672 done 673 674 675 # Execute Android tests 676 if [[ "$EAS_BUILD_PROFILE" == "test" ]]; then 677 detox test --configuration android.release 678 fi 679 if [[ "$EAS_BUILD_PROFILE" == "test_debug" ]]; then 680 detox test --configuration android.debug 681 fi 682else 683 # Execute iOS tests 684 if [[ "$EAS_BUILD_PROFILE" == "test" ]]; then 685 detox test --configuration ios.release 686 fi 687 if [[ "$EAS_BUILD_PROFILE" == "test_debug" ]]; then 688 detox test --configuration ios.debug 689 fi 690fi 691``` 692 693</Collapsible> 694 695<Collapsible summary="eas.json"> 696 697```json 698{ 699 "build": { 700 "test": { 701 "android": { 702 "gradleCommand": ":app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release", 703 "withoutCredentials": true 704 }, 705 "ios": { 706 "simulator": true 707 } 708 }, 709 /* @info New section */ "test_debug": { 710 "android": { 711 "gradleCommand": ":app:assembleDebug :app:assembleAndroidTest -DtestBuildType=debug", 712 "withoutCredentials": true 713 }, 714 "ios": { 715 "buildConfiguration": "Debug", 716 "simulator": true 717 }, 718 "env": { 719 "EXPO_USE_UPDATES": "1" 720 }, 721 "channel": "test_debug" 722 } /* @end */ 723 } 724} 725``` 726 727</Collapsible> 728