Skip to main content
No Result Found
Connect & Get help from fellow developers on our Discord community. Ask the CommunityAsk the Community

Smart TV testing on BrowserStack App Automate

A guide to running your Appium tests on smart TV devices with BrowserStack App Automate.

Important: BrowserStack currently supports an alpha version of smart TV testing, and thus, it is not available to all users yet. If you want to get access to smart TV testing, contact support and request access to the Smart TV alpha release.

App Automate lets you test your apps on smart TV. We currently support testing apps on:

  • Amazon Fire TV Stick 4K (Android v7.1)
  • Nvidia Shield TV Pro 2019 (Android v11.0)
  • Apple TV 4k (tvOS v16.3)
  • Roku TV (RokuOS 14.6). For Roku TV, you require Appium version 2.18.0. Also, refer to this Roku Driver to know the variety of interaction you can do with Roku devices.

1. Setup your environment

  • Access to the alpha version of smart TV testing
  • You will need a BrowserStack username and access key. To obtain your access credentials, sign up for a free trial or purchase a plan.
  • Access to a smart TV app (.apk or .ipa file, .zip file for Roku TV).

2. Upload your app

Note:

Upload your app (.apk/.ipa/.zip for Roku TV) file to BrowserStack servers using our REST API request as follows:

curl -u "YOUR_USERNAME:YOUR_ACCESS_KEY" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "file=@/path/to/app/file"

# Ensure that `@` symbol is prepended to the file path in the request.
curl -u "YOUR_USERNAME:YOUR_ACCESS_KEY" ^
-X POST "https://api-cloud.browserstack.com/app-automate/upload" ^
-F "file=@/path/to/app/file"

# Ensure that `@` symbol is prepended to the file path in the request.

A sample response for the request is as follows:

{
    "app_url":"bs://j3c874f21852ba57957a3fdc33f47514288c4ba4"
}

Note the value of app_url returned in the response to the REST API request. This value will be used later to specify the application under test for your test execution.

Important: App upload might take a few seconds to about a minute depending on the size of your app. Do not interrupt the cURL command until you get the response in your command-line/terminal.

3. Configure and run your test

In this step, you will learn how to configure your Appium test script using desired capabilities to test remotely on BrowserStack’s real device cloud along with a sample test script.

Update your test script with the following changes:

  • Set the app capability to the app_url of the app you uploaded
  • Set device capability to either of the following two options:
    • Amazon Fire TV Stick 4K - run tests on Fire TV
    • Nvidia Shield TV Pro 2019 - run tests on Nvidia Shield TV
    • Apple TV 4k - run tests on Apple TV 4k
    • Roku TV - run tests on RokuOS 14.6
  • Initialize an Appium driver using a remote BrowserStack URL along with your BrowserStack credentials as follows:
      https://YOUR_USERNAME:YOUR_ACCESS_KEY@hub.browserstack.com/wd/hub
    

Sample test scripts

The following sample Ruby test script includes device capabilities to run a test on the Fire TV, an Apple TV app, and Roku TV.

import io.appium.java_client.AppiumBy;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.nativekey.AndroidKey;
import io.appium.java_client.android.nativekey.KeyEvent;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.openqa.selenium.By;
import org.openqa.selenium.MutableCapabilities;
import io.appium.java_client.android.AndroidDriver;
import java.net.URL;
import static org.openqa.selenium.By.*;

public class SmartTVTest {

    public void testSmartTV() throws Exception {
        AndroidDriver driver;

        MutableCapabilities caps = new UiAutomator2Options();
        caps.setCapability("build", "Smart TV Test");
        caps.setCapability("deviceName", "Amazon Fire TV Stick 4K");
        caps.setCapability("osVersion", "7.1");
        caps.setCapability("appium:browserstack.dedicatedDevice", true);
        caps.setCapability("app", APP_ID);

        driver = new AndroidDriver(new URL("https://" +
                BROWSERSTACK_USER + ":" + BROWSERSTACK_ACCESS_KEY +
                "@hub.browserstack.com/wd/hub"), caps);


        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_CENTER));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_LEFT));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_LEFT));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_RIGHT));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_RIGHT));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_RIGHT));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_RIGHT));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_CENTER));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_RIGHT));

        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_CENTER));
        Thread.sleep(5000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.DPAD_CENTER));
        Thread.sleep(10000);
        driver.pressKey(new KeyEvent().withKey(AndroidKey.MEDIA_PLAY_PAUSE));

        driver.quit();
    }
}

require 'rubygems'
require 'appium_lib'

username = 'YOUR_USERNAME'
access_key = 'YOUR_ACCESS_KEY'

desired_caps = {
    'build': 'Ruby Appium Sample',
    'device': 'Amazon Fire TV Stick 4K',
    'osVersion': '7.1',
    'browserstack.debug': 'true',
    'app': '<app_hashed_id>'
}

appium_driver = Appium::Driver.new({
    'caps' => desired_caps,
    'appium_lib' => {
        :server_url => "https://#{username}:#{access_key}@hub-cloud.browserstack.com/wd/hub"
    }}, true)
driver = appium_driver.start_driver

sleep 5
driver.press_keycode(20) # DPAD_DOWN
driver.press_keycode(22) # DPAD_RIGHT

video_element = driver.find_element(:xpath, "//android.widget.FrameLayout[@focused = 'true']")
video_name = video_element.attribute('content-desc')
puts "Selected Video Name: #{video_name}"

driver.press_keycode(23) # DPAD_CENTER
sleep(5)
driver.press_keycode(85) # MEDIA_PLAY_PAUSE
sleep(3)
driver.press_keycode(85) # MEDIA_PLAY_PAUSE
sleep(3)
driver.press_keycode(89) # MEDIA_REWIND
sleep(3)
driver.press_keycode(4) # BACK

focussed_element = driver.find_element(:xpath, "//android.widget.FrameLayout[@focused = 'true']")
focussed_video_name = focussed_element.attribute('content-desc')

if focussed_video_name == video_name
  puts "Video played - Test passed"
else
  puts "Video Could not be played - Test Failed"
end

driver.quit
const webdriverio = require('webdriverio');

const opts = {
  port: 4723,
  capabilities: {
    platformName: 'Android',
    platformVersion: '7.1',
    deviceName: 'Amazon Fire TV Stick 4K',
    app: '<app_hashed_id>',
    'browserstack.debug': 'true',
  },
  path: '/wd/hub',
  user: 'YOUR_USERNAME',
  key: 'YOUR_ACCESS_KEY',
};

(async () => {
  const client = await webdriverio.remote(opts);

  await client.pause(5000);
  await client.pressKeyCode(20); // DPAD_DOWN
  await client.pressKeyCode(22); // DPAD_RIGHT

  const videoElement = await client.$(
    '//android.widget.FrameLayout[@focused = "true"]',
  );
  const videoName = await videoElement.getAttribute('content-desc');
  console.log(`Selected Video Name: ${videoName}`);

  await client.pressKeyCode(23); // DPAD_CENTER
  await client.pause(5000);
  await client.pressKeyCode(85); // MEDIA_PLAY_PAUSE
  await client.pause(3000);
  await client.pressKeyCode(85); // MEDIA_PLAY_PAUSE
  await client.pause(3000);
  await client.pressKeyCode(89); // MEDIA_REWIND
  await client.pause(3000);
  await client.pressKeyCode(4); // BACK

  const focussedElement = await client.$(
    '//android.widget.FrameLayout[@focused = "true"]',
  );
  const focussedVideoName = await focussedElement.getAttribute('content-desc');
  if (focussedVideoName === videoName) {
    console.log('Video played - Test passed');
  } else {
    console.log('Video Could not be played - Test Failed');
  }

  await client.deleteSession();
})();

Important: Appium 2.0 is the default and the only version supported by App Automate on tvOS, which comes with W3C capabilities.
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.ios.IOSElement;
import io.appium.java_client.remote.MobileCapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import io.appium.java_client.ios.IOSDriver;

import java.net.URL;
import java.util.concurrent.TimeUnit;

import java.util.HashMap;

public class TVOSTest {

    public static String USERNAME = "<your-username>";
    public static String ACCESS_KEY = "<your-access-key>";

    public static void main(String[] args) throws Exception {
    DesiredCapabilities capabilities = new DesiredCapabilities();
    HashMap<String, Object> browserstackOptions = new HashMap<String, Object>();
    browserstackOptions.put("projectName" , "TVOS-PROJECT");
    browserstackOptions.put("buildName" , "TVOS-BUILD");
    browserstackOptions.put("sessionName" , "TVOS-SESSION");
    browserstackOptions.put("networkLogs" , true);
    capabilities.setCapability("bstack:options" , browserstackOptions);
    capabilities.setCapability("platformName" , "tvOS");
    capabilities.setCapability("platformVersion" , "16.3");
    capabilities.setCapability("deviceName" , "Apple TV 4K");
    capabilities.setCapability(MobileCapabilityType.APP , "<enter app url here>");
    capabilities.setCapability("automationName" , "XCUITest");
        
    IOSDriver driver = new IOSDriver(new URL("https://" + USERNAME + ":" + ACCESS_KEY +
            "@hub-cloud.browserstack.com/wd/hub"), capabilities);

    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    System.out.println(driver.getPageSource());
    driver.getScreenshotAs(OutputType.FILE);
    driver.quit();
    }
}
require 'rubygems'
require 'appium_lib'
require 'selenium-webdriver'
# Use Ruby Client version v9.8.1 or above

username = 'YOUR_USERNAME'
access_key = 'YOUR_ACCESS_KEY'

caps = {
	"platformName" => "tvos",
	"platformVersion" => "16.3",
	"deviceName" => "Apple TV 4K",
    "app" => "<enter app url here>", 
    "automationName" => "xcuitest",
	'bstack:options' => {
		"networkLogs" => "true",
		"projectName" => "TVOS-PROJECT",
		"buildName" => "TVOS-BUILD",
		"sessionName" => "TVOS-SESSION",
	},
}


appium_driver = Appium::Driver.new({
	'caps' => caps,
	'appium_lib' => {
		:server_url => "https://#{username}:#{access_key}@hub-cloud.browserstack.com/wd/hub",
	}}, true)
driver = appium_driver.start_driver
wait = Selenium::WebDriver::Wait.new(:timeout => 30)

driver.page_source
driver.execute_script 'mobile: pressButton', { name: 'Down' }
driver.execute_script 'mobile: pressButton', { name: 'Down' }
driver.execute_script 'mobile: pressButton', { name: 'Left' }
driver.execute_script 'mobile: pressButton', { name: 'Left' }
driver.execute_script 'mobile: pressButton', { name: 'Select' }
sleep(5)
playButton = driver.find_element(:xpath, '//XCUIElementTypeButton[@name="Play"]')
puts playButton.text
playButton.click()
sleep(10)
driver.quit
const webdriver = require('selenium-webdriver');
const { Builder } = webdriver;

const username = 'YOUR_USERNAME';
const accessKey = 'YOUR_ACCESS_KEY';

const capabilities = {
  'platformName': 'tvos',
  'platformVersion': '16.3',
  'deviceName': 'Apple TV 4K',
  'app': '<enter app url here>',
  'automationName': 'xcuitest',
  'bstack:options': {
    'networkLogs': 'true',
    'projectName': 'TVOS-PROJECT',
    'buildName': 'TVOS-BUILD',
    'sessionName': 'TVOS-SESSION'
  }
};

const driver = new Builder()
  .usingServer(`https://${username}:${accessKey}@hub-cloud.browserstack.com/wd/hub`)
  .withCapabilities(capabilities)
  .build();

driver.getPageSource();
driver.executeScript('mobile: pressButton', { name: 'Down' });
driver.executeScript('mobile: pressButton', { name: 'Down' });
driver.executeScript('mobile: pressButton', { name: 'Left' });
driver.executeScript('mobile: pressButton', { name: 'Left' });
driver.executeScript('mobile: pressButton', { name: 'Select' });
driver.sleep(5000);
const playButton = driver.findElement(webdriver.By.xpath('//XCUIElementTypeButton[@name="Play"]'));
playButton.getText().then(text => {
  console.log(text);
  playButton.click();
  driver.sleep(10000);
  driver.quit();
});

Important: For Roku TV, you require Appium version 2.18.0.
import io.appium.java_client.AppiumDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class RokuTest {
    public static void main(String[] args) {
        String username = "<UserName>";
        String accessKey = "<AccessKey>";
        String app_url = "<AppURL>";
        String device_ID = "<DeviceID>";

        AppiumDriver driver = null;

        try {
            DesiredCapabilities capabilities = new DesiredCapabilities();
            capabilities.setCapability("platformName", "Roku");
            capabilities.setCapability("appium:deviceName", "Roku Express 4K");
            capabilities.setCapability("appium:app", app_url);
            capabilities.setCapability("appium:automationName", "Roku");
            capabilities.setCapability("appium:platform", "roku");

            Map<String, Object> bstackOptions = new HashMap<>();
            bstackOptions.put("projectName", "Roku");
            bstackOptions.put("buildName", "<your build name>");
            bstackOptions.put("idleTimeout", "300");
            bstackOptions.put("dedicatedDevice", true);
            bstackOptions.put("deviceId", device_ID);
            bstackOptions.put("osVersion", "14.6");
            capabilities.setCapability("bstack:options", bstackOptions);

            String serverUrl = "https://" + username + ":" + accessKey + "@hub-cloud.browserstack.com/wd/hub";
            driver = new AppiumDriver(new URL(serverUrl), capabilities);

            // Activate app
            Map<String, Object> activateAppArgs = new HashMap<>();
            activateAppArgs.put("appId", "dev");
            driver.executeScript("roku: activateApp", activateAppArgs);

            // Get page source
            String pageSource = driver.getPageSource();
            System.out.println("Page Source:\n" + pageSource);

            // Find label element
            WebElement label = driver.findElement(By.xpath("//Label[contains(@text, 'Hello World')]"));
            System.out.println("Label text: " + label.getAttribute("text"));
            System.out.println("Label bounds: " + label.getAttribute("bounds"));

            driver.findElement(By.xpath("//HelloWorld"));

            WebElement screen = driver.findElement(By.xpath("//screen"));
            System.out.println("Screen focused? " + screen.getAttribute("focused"));

            // Press Home key
            Map<String, Object> pressKeyArgs = new HashMap<>();
            pressKeyArgs.put("key", "Home");
            driver.executeScript("roku: pressKey", pressKeyArgs);

        } catch (MalformedURLException e) {
            System.err.println("Invalid URL: " + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            System.err.println("An error occurred: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // Invoke driver.quit() after the test is done to indicate that the test is completed.
            if (driver != null) {
                try {
                    driver.quit();
                    // Give threads time to cleanup
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
}
require 'rubygems'
require 'appium_lib'
require 'selenium-webdriver'

username = '<USERNAME>'
access_key = '<ACCESS_KEY>'
device_id = '<DEVICE_ID>'
app_url = '<APP_URL>'


capabilities = {
  'platformName' => 'Roku',
  'appium:deviceName' => 'Roku Express 4K',
  'appium:app' => app_url, # local zip
  'appium:automationName' => 'Roku',
  'bstack:options' => {
    'projectName' => 'Roku',
    'buildName' => 'RokuTest1',
    'idleTimeout' => '300',
    'dedicatedDevice' => true,
    'deviceId' => device_id
  },
  'appium:platform' => 'roku',
  'device' => 'Roku Express 4K',
  'os_version' => '14.6'
}


# BrowserStack's Appium server URL (not localhost)

server_url = "https://#{username}:#{access_key}@hub-cloud.browserstack.com/wd/hub"
# Initialize the driver with W3C capabilities


appium_driver = Appium::Driver.new({
                                     caps: capabilities,
                                     appium_lib: {
                                       server_url: server_url
                                     }
                                   }, true)

driver = appium_driver.start_driver

begin
  driver.execute_script('roku: activateApp', { appId: 'dev' })

  page_source = driver.page_source
  puts "Page Source:\n#{page_source}"


  label = driver.find_element(:xpath, "//Label[contains(@text, 'Hello World')]")
  puts "Label text: #{label.attribute('text')}"
  puts "Label bounds: #{label.attribute('bounds')}"
  driver.find_element(:xpath, '//HelloWorld')
  screen = driver.find_element(:xpath, '//screen')
  puts "Screen focused? #{screen.attribute('focused')}"

  driver.execute_script('roku: pressKey', { key: 'Home' })
rescue StandardError => e
  puts "An error occurred: #{e.message} e.backtrace: #{e.backtrace.join("\n")}"
end
# Invoke driver.quit() after the test is done to indicate that the test is completed.
driver.quit
const { remote } = require("webdriverio");
const assert = require("assert");

const username = "<username>";
const accessKey = "<accessKey>";
const app_url = "<AppURL>";
const device_ID = "<DeviceID>";
const capabilities = {
  platformName: "Roku",
  "appium:deviceName": "Roku Express 4K",
  "appium:app": app_url,
  "appium:automationName": "Roku",
  "appium:platform": "roku",
  "bstack:options": {
    projectName: "Roku",
    buildName: "<your build name>",
    idleTimeout: "300",
    dedicatedDevice: true,
    deviceId: device_ID,
    osVersion: "14.6",
  },
};

const serverUrl = `https://${username}:${accessKey}@hub-cloud.browserstack.com/wd/hub`;

async function runRokuTest() {
  let driver;

  try {
    driver = await remote({
      protocol: "https",
      hostname: "hub-cloud.browserstack.com",
      port: 443,
      path: "/wd/hub",
      user: username,
      key: accessKey,
      capabilities: capabilities,
      logLevel: "info",
    });

    try {
      // Get device info for logging
      const deviceName = capabilities["appium:deviceName"];
      const deviceVersion = capabilities["bstack:options"].osVersion;
      console.log(
        `Running Roku TV Test on ${deviceName} - ${deviceVersion}`
      );
      // Activate App
      await driver.pause(2000);
      await driver.execute("roku: activateApp", { appId: "dev" });

      // Get the current page source
      const pageSource = await driver.getPageSource();
      assert(
        pageSource.includes('name="Hello World"'),
        "Page source should include Hello World!"
      );

      // Locate Label element by XPath
      const label = await driver.$("//label[contains(@text, 'Hello World')]");
      const labelText = await label.getText();
      const labelTextAttr = await label.getAttribute("text");
      const labelBounds = await label.getAttribute("bounds");
      assert(
        labelText.includes("Hello World!"),
        labelText + " should include Hello World!"
      );
      assert(
        labelTextAttr.includes("Hello World!"),
        labelTextAttr + " should include Hello World!"
      );
      assert(labelBounds.includes("{0, 0, 1280, 720}"));

      // Take screenshot as base64
      const screenshot = await driver.takeScreenshot();
      if (screenshot) {
        console.log("Screenshot data:", screenshot);
      }

      // Locate scene and screen elements
      const scene = await driver.$("//HelloWorld");
      const sceneFocused = await scene.getAttribute("focused");
      assert(sceneFocused.includes("true"), "Scene should be focused!");

      // Press Home key on Roku device
      await driver.execute("roku: pressKey", { key: "Home" });
      console.log("Home key pressed");
      console.log("Waiting for 5 seconds to observe the Roku device...");

      // roku: deviceInfo
      const requiredKeys = [
        "device-id",
        "model-name",
        "model-number",
        "model-region",
      ];
      const deviceInfo = await driver.execute("roku: deviceInfo");
      requiredKeys.forEach((key) => {
        assert(
          deviceInfo.hasOwnProperty(key),
          `${key} should be present in deviceInfo`
        );
      });

      // roku: getApps
      const apps = await driver.execute("roku: getApps");
      const appExists = apps.some((app) => app.name === "Hello World");
      assert(
        appExists,
        "Hello World app should be installed on the Roku device"
      );

      // await driver.pause(100000);
      await driver.execute(
        'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "passed", "reason": "Roku TV Test Passed" }}'
      );
    } catch (err) {
      console.log(err);
      message = err.message.replace(/[^a-zA-Z0-9 ]/g, "");
      await driver.execute(
        'browserstack_executor: {"action": "setSessionStatus", "arguments": {"status": "failed", "reason": "' +
          message +
          '" }}'
      );
      assert(false, err.message);
    }
  } finally {
    // Invoke driver.quit() after the test is done to indicate that the test is completed.
    if (driver) {
      await driver.deleteSession();
    }
  }
}

runRokuTest();

4. View test execution results

After you start the test execution, visit your App Automate dashboard to view your test results.

You can drill down into the details of a specific test session to view its execution details and debugging information such as video recording, screenshots and appium logs.

You can also use API requests to view test results.

Need some help?

If you have any queries, contact support.

We're sorry to hear that. Please share your feedback so we can do better

Contact our Support team for immediate help while we work on improving our docs.

We're continuously improving our docs. We'd love to know what you liked





Thank you for your valuable feedback

Is this page helping you?

Yes
No

We're sorry to hear that. Please share your feedback so we can do better

Contact our Support team for immediate help while we work on improving our docs.

We're continuously improving our docs. We'd love to know what you liked





Thank you for your valuable feedback!

Talk to an Expert
Download Copy Check Circle