Smart TV testing on BrowserStack App Automate
A guide to running your Appium tests on smart TV devices with BrowserStack App Automate.
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
- If you want to test smart TVs, download and test our sample app for Android and our sample app for tvOS.
- If you want to test a Roku TV app, you can download our sample app for Roku TV.
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.
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 theapp_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();
})();
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();
});
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
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!