<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Stb-tester blog</title>
    <description>
      Stb-tester news, product announcements, and testing topics.
    </description>
    <link>https://stb-tester.com/blog/</link>
    <atom:link href="https://stb-tester.com/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 16 Apr 2026 13:35:06 +0100</pubDate>
    <lastBuildDate>Thu, 16 Apr 2026 13:35:06 +0100</lastBuildDate>
    <generator>Jekyll v4.3.2</generator>
    
      <item>
        <title>Stb-tester implements UVQ for Video Quality Assessment</title>
        <pubDate>Thu, 16 Apr 2026 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2026/04/16/uvq</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2026/04/16/uvq</guid>
        <description>
          &lt;p&gt;UVQ is a no-reference perceptual video-quality assessment model developed by
Google Research / YouTube.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>UVQ is a no-reference perceptual video-quality assessment model developed by
Google Research / YouTube.</p>

<p>“No-reference” means that you don’t have to provide a “reference” or “original”
video to compare the content against — the model works with any live content.
“Perceptual” means that the model tries to give a rating that would match a
human’s perception of the video quality.</p>

<p>UVQ returns a “Mean Opinion Score” (MOS), which is a number between 1 and 5:</p>

<table class="table table-condensed">
  <thead>
    <tr>
      <th>Example video</th>
      <th>Bitrate</th>
      <th>UVQ MOS</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><video controls="" autoplay="" muted="" src="https://stb-tester.com/blog/images/uvq/720p.mov"></video></td>
      <td>6 Mbps</td>
      <td>4.7</td>
    </tr>
    <tr>
      <td><video controls="" autoplay="" muted="" src="https://stb-tester.com/blog/images/uvq/480p.mov"></video></td>
      <td>3 Mbps</td>
      <td>4.3</td>
    </tr>
    <tr>
      <td><video controls="" autoplay="" muted="" src="https://stb-tester.com/blog/images/uvq/180p.mov"></video></td>
      <td>0.9 Mbps</td>
      <td>2.9</td>
    </tr>
  </tbody>
</table>

<p>The Stb-tester Node has a decent GPU, so our UVQ implementation runs in near real-time, allowing you to sample about 1 second of video out of every 5 seconds.</p>

<p>To learn more:</p>

<ul>
  <li><a href="https://research.google/blog/uvq-measuring-youtubes-perceptual-video-quality/">UVQ: Measuring YouTube’s Perceptual Video Quality</a>
on the Google Research blog has high-level information about the algorithm.</li>
  <li><a href="https://stb-tester.com/manual/python-api#stbt.UVQ">stbt.UVQ</a> API documentation in the Stb-tester manual.</li>
</ul>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Using ADB for remote control</title>
        <pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2026/03/09/adb</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2026/03/09/adb</guid>
        <description>
          &lt;p&gt;The “Manual control” interface of the Stb-tester Portal now supports using ADB
to control Android devices; and the Stb-tester Node hardware now supports ADB
over USB.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>The “Manual control” interface of the Stb-tester Portal now supports using ADB
to control Android devices; and the Stb-tester Node hardware now supports ADB
over USB.</p>

<p>We have supported ADB from your test scripts for many years, but only over the
network. The new features are:</p>

<ul>
  <li>
    <p>ADB over USB: This is more reliable than Network ADB, and doesn’t require any
configuration (with Network ADB you need to configure the device-under-test’s
IP address). Simply use a USB cable to connect the Android device to the
Stb-tester Node.</p>
  </li>
  <li>
    <p>ADB for remote control from the interactive web interface of the Stb-tester
Portal. (Previously ADB was only accessible from your Python test scripts.)</p>
  </li>
</ul>

<p>We still recommend using infrared, HDMI CEC, or Bluetooth for remote control
when possible, as these are more representative of real-world usage. Using ADB
for other purposes (such as <a href="https://stb-tester.com/manual/logs">collecting logs with logcat</a>) is, of course,
a good idea, and now it’s more convenient if your Android device supports USB.</p>

<p>For configuration instructions see <a href="https://stb-tester.com/manual/remote-control#adb-control">Remote Control Configuration: ADB
control</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Meet Stb-tester at IBC (Amsterdam, 12-15 September 2025)</title>
        <pubDate>Wed, 09 Jul 2025 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2025/07/09/ibc</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2025/07/09/ibc</guid>
        <description>
          &lt;p&gt;Stb-tester will be exhibiting for the first time at the &lt;a href=&quot;https://show.ibc.org/&quot;&gt;IBC tradeshow&lt;/a&gt; in
Amsterdam, 12-15 September 2025.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester will be exhibiting for the first time at the <a href="https://show.ibc.org/">IBC tradeshow</a> in
Amsterdam, 12-15 September 2025.</p>

<p>Find us at stand <strong>14.B57</strong> in the Future Tech hall. Drop by to see Stb-tester’s
latest innovations in automated testing: how our private, on-device AI models
can be adopted incrementally in your existing test scripts; our new Bluetooth
remote-control capabilities; and more.</p>

<p>Whether you’re a broadcaster, streaming operator, middleware developer or device
manufacturer, we’d love to show you what’s new and hear about your testing
challenges. Contact us at <a href="mailto:ibc@stb-tester.com">ibc@stb-tester.com</a> or
<a href="https://calendly.com/stb-tester/ibc2025" class="btn btn-primary">Book
a meeting</a>.</p>

<h3 id="about-stb-tester">About Stb-tester</h3>

<p>Stb-tester is a best-in-class test automation tool for testing TV &amp; streaming
apps on real devices such as Android TV, Apple TV, Fire TV, PlayStation, Roku,
XBox, and traditional Set-Top Boxes. Founded in 2013, with uninterrupted
year-on-year growth, Stb-tester is used by leading broadcasters, ISPs, and
streaming operators worldwide.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester&apos;s new AI model for page classification</title>
        <pubDate>Mon, 19 May 2025 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2025/05/19/ai-classification</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2025/05/19/ai-classification</guid>
        <description>
          &lt;p&gt;Before today, to detect which page of the application is currently visible,
your test script would use Stb-tester APIs such as image-matching to detect
a particular reference image; but when the page is very dynamic, this can be
difficult to do. Stb-tester has a new AI that can be trained to recognize each
page of your application-under-test. Watch this video to see how it works:&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Before today, to detect which page of the application is currently visible,
your test script would use Stb-tester APIs such as image-matching to detect
a particular reference image; but when the page is very dynamic, this can be
difficult to do. Stb-tester has a new AI that can be trained to recognize each
page of your application-under-test. Watch this video to see how it works:</p>

<video controls="" preload="metadata" src="https://stb-tester.com/blog/images/ai/ai-announcement.mp4" poster="/blog/images/ai/ai-announcement.jpg" class="border"></video>

<h2 id="local-training--inference">Local training &amp; inference</h2>

<p>Stb-tester’s AI model trains &amp; runs directly on your Stb-tester Node hardware —
it isn’t sending each frame of video to the cloud for processing. This allows
privacy and low latency (the model runs at about 20 frames per second).</p>

<p>There is a new training interface in the Stb-tester Portal, that allows very
quickly collecting the necessary training screenshots, and verifying the
behaviour of the model interactively.</p>

<h2 id="how-to-use-it-in-your-python-test-scripts">How to use it in your Python test scripts</h2>

<p>The <a href="https://stb-tester.com/manual/python-api#stbt.FrameObject">stbt.FrameObject</a> base class for your Page Objects has a default
implementation of the “<code class="language-plaintext highlighter-rouge">is_visible</code>” property. Before, each of your Page Objects
had to implement this property, perhaps using <code class="language-plaintext highlighter-rouge">stbt.match</code> like this:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class Settings(stbt.FrameObject):
<span class="gd">-    @property
-    def is_visible(self):
-        return bool(stbt.match("Settings.png", frame=self._frame))
</span></code></pre></div></div>

<p>Now, if you remove the <code class="language-plaintext highlighter-rouge">is_visible</code> property from your Page Object, it will
use the default implementation, which asks the AI model if the current page is
visible:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Settings</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="k">pass</span>
</code></pre></div></div>

<p>Class names are mapped to the AI labels as “device or application name” slash
“Python class name”, for example “appletv/Settings”. The application name comes
from the directory layout of your Python code, like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tests/{app_name}/pages/{module_name}/{module_name}.py
</code></pre></div></div>

<p>If you are using Stb-tester’s <a href="https://stb-tester.com/manual/test-pack-structure">standard directory layout</a>, this mapping works
automatically. (We also support a few other directory layouts used by existing
customers with large code-bases, to ease adoption of new Object Repository and
AI features.)</p>

<p>This is Stb-tester’s first AI model, and it only applies to the “<code class="language-plaintext highlighter-rouge">is_visible</code>”
property. Stay tuned for more AI features to come!</p>

<h2 id="your-ai-model-is-version-controlled">Your AI model is version-controlled</h2>

<p>The training screenshots are stored in your test-pack git repository under the
folder <code class="language-plaintext highlighter-rouge">ai/is_visible/training/</code>, so whenever you run a test it will use the
model that was trained from that specific git commit.</p>

<p>This way, the behaviour of your tests continues to be version-controlled and
reproducible — which, in our opinion, are fundamental requirements for any
serious test automation.</p>

<h2 id="try-it-out">Try it out!</h2>

<p>You can adopt this new AI model incrementally. Use it for new development —any
new Page Objects you write— or if you have an existing Page Object that is
unreliable, or is taking a lot of maintenance effort, then try replacing it with
AI!</p>

<p>We expect this new AI feature to speed up your test development and simplify
your Page Object code significantly, while integrating seamlessly with your
existing test scripts and emphasizing a “code first” approach of managing test
automation as code. We plan to iterate quickly on ever more powerful and general
AI models, so please send your feedback to <a href="mailto:support@stb-tester.com">support@stb-tester.com</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Selenium for second-screen login flows</title>
        <pubDate>Wed, 30 Apr 2025 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2025/04/30/selenium</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2025/04/30/selenium</guid>
        <description>
          &lt;p&gt;You can use Selenium to automate browser-based user journeys on a second screen,
from the same Stb-tester Python script that is automating the big screen
journey. For example, many TV apps use a browser-based login flow, where the
TV screen shows a code that you type into a browser on your phone or computer.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>You can use Selenium to automate browser-based user journeys on a second screen,
from the same Stb-tester Python script that is automating the big screen
journey. For example, many TV apps use a browser-based login flow, where the
TV screen shows a code that you type into a browser on your phone or computer.</p>

<p>Here’s a short demo signing into the BBC iPlayer app. We use Stb-tester’s <a href="https://stb-tester.com/manual/python-api#stbt.ocr">ocr</a>
API to read the sign-in code from the TV screen, and then we enter it into the
browser using Selenium.</p>

<video controls="" preload="metadata" src="https://stb-tester.com/manual/_static/selenium/iplayer_login.webm" poster="/manual/_static/selenium/iplayer_login.jpg" class="border"></video>

<p>For detailed instructions see <a href="https://stb-tester.com/manual/selenium">Selenium for second-screen login flows</a> in the
Stb-tester manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Device-specific configuration for the Object Repository</title>
        <pubDate>Mon, 28 Apr 2025 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2025/04/28/device-profile</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2025/04/28/device-profile</guid>
        <description>
          &lt;p&gt;Today we have published improvements to Stb-tester’s Object Repository to better
support testing the same device or app across different territories, languages,
and software releases. We have also improved the way that the Stb-tester Portal
web interface displays information about each device in your test farm.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Today we have published improvements to Stb-tester’s Object Repository to better
support testing the same device or app across different territories, languages,
and software releases. We have also improved the way that the Stb-tester Portal
web interface displays information about each device in your test farm.</p>

<p>We have always recommended specifying the device type (e.g. “appletv”) in the
<a href="https://stb-tester.com/manual/configuration#node-specific-config">Node-specific config file</a> of each Stb-tester Node, in the
<em>device_under_test.device_type</em> configuration key. This was a free-form field
that you can read in your test scripts to make decisions such as which reference
images to use, OCR language, etc.</p>

<p>Today we are broadening that recommendation to an entire configuration section
called “device_profile”. This section is treated specially in the following
ways:</p>

<ol>
  <li>When you save a screenshot to the Object Repository, Stb-tester will record
the entire <em>device_profile</em> config section (copied from the <a href="https://stb-tester.com/manual/configuration#node-specific-config">Node-specific
config file</a> of the Node where the screenshot came from) as well as the
<em>device_under_test.device_type</em> and <em>ocr.lang</em> config values. This allows the
Object Repository to use the correct OCR language for that screenshot when
testing your Page Objects; and your Page Object properties can read other
values from this section.</li>
  <li>For convenience, the Stb-tester Portal web interface can show the
<em>device_type</em> or selected values from the <em>device_profile</em> configuration of
each Node in the Nodes menu, the “video wall” or “virtual NOC” page, and the
admin page. This is controlled by the new <a href="https://stb-tester.com/manual/configuration#test-pack-node-label-format">test_pack.node_label_format</a>
setting.</li>
</ol>

<p>We have also changed the way that the <em>node.friendly_name</em> configuration is
displayed in the Stb-tester Portal web interface:</p>

<ol start="3">
  <li>The Node’s “friendly name” is now taken from the config files on the current
git branch — previously the web interface always showed the friendly name
from the main branch.</li>
  <li>Similarly, editing the Node’s “friendly name” in the admin page will write
the new value to the config files on the current git branch, instead of
the main branch.</li>
</ol>

<p>Changes 3 &amp; 4 were made for consistency with the new <em>node_label_format</em> and
with the existing behaviour of <code class="language-plaintext highlighter-rouge">stbt.get_config("node", "friendly_name")</code> when
running a test script. This only affects the web interface; the <em>friendly_name</em>
value in the response of the <a href="https://stb-tester.com/manual/rest-api-v2#get--api-v2-nodes">/api/v2/nodes</a> REST API endpoint is always taken
from the main branch (no change in behaviour there, as it’s a public API).</p>

<h2 id="motivation-configuration-for-the-object-repository">Motivation: Configuration for the Object Repository</h2>

<p>When you run a test script against a real device, you might need to configure
certain settings differently depending on the device. For example, if you are
testing devices in different territories, you need to configure the language for
OCR. This is done through the <a href="https://stb-tester.com/manual/configuration#node-specific-config">Node-specific config files</a>, which are files
named after the serial number of each Stb-tester Node, like this:</p>

<figure>
  <figcaption class="left">
    <p>config/test-farm/stb-tester-xxxxxxxxxxxx.conf</p>
  </figcaption>
  <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[device_profile]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">Roku Mexico</span>
<span class="nn">[ocr]</span>
<span class="py">lang</span> <span class="p">=</span> <span class="s">spa</span>
</code></pre></div>  </div>
</figure>

<p>When you run a test, the configuration is loaded to match the Node that is
running the test. This is true for Stb-tester APIs such as <a href="https://stb-tester.com/manual/python-api#stbt.ocr">stbt.ocr</a> which
reads the “lang” setting, and it’s true for configuration that you read
explicitly by calling <a href="https://stb-tester.com/manual/python-api#stbt.get_config">stbt.get_config</a>.</p>

<p>However, when you are testing your Page Object code in the Object Repository,
the code isn’t running on any particular Node; instead of a live
device-under-test the Page Object code is tested against a screenshot. You want
the configuration (such as OCR language) to match the device the screenshot came
from. When you save a screenshot to the Object Repository, Stb-tester
automatically records the following configuration:</p>

<ul>
  <li><strong>ocr.lang</strong></li>
  <li><strong>device_under_test.device_type</strong></li>
  <li>The entire <strong>device_profile</strong> section</li>
</ul>

<p>This configuration is saved into a “.conf” file next to the screenshot (for
example <code class="language-plaintext highlighter-rouge">selftest/screenshots/roku/Menu/screenshot.png.conf</code>).</p>

<p>We don’t copy <em>all</em> of the configuration, only the above values, to minimize
maintenance overhead. The Object Repository will continue to read common
configuration (in <a href="https://stb-tester.com/manual/configuration#stbt-conf">.stbt.conf</a>) from the current values on the selected branch;
whereas the above values are frozen in time when you save the screenshot.</p>

<p>To recap:</p>

<p>When you run a test, calling <a href="https://stb-tester.com/manual/python-api#stbt.get_config">stbt.get_config</a> will read from these files on the
git branch that you are running the test from:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">.stbt.conf</code> (global config)</li>
  <li><code class="language-plaintext highlighter-rouge">config/test-farm/${node_id}.conf</code> (the Node-specific config file for the Node
running the test)</li>
</ul>

<p>In the Object Repository, calling <a href="https://stb-tester.com/manual/python-api#stbt.get_config">stbt.get_config</a> will read from these files
instead:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">.stbt.conf</code> (global config)</li>
  <li><code class="language-plaintext highlighter-rouge">selftest/screenshots/${path_to_screenshot_png}.conf</code> (recorded from the
Node-specific config file at the time the screenshot was taken)</li>
</ul>

<h2 id="recommendations">Recommendations</h2>

<p>Any configuration that varies between different devices <strong>and</strong> needs to be read
from Page Object properties, should be placed in the <em>device_profile</em> section
of the <a href="https://stb-tester.com/manual/configuration#node-specific-config">Node-specific config file</a> of each Node.</p>

<p>Note that Page Object properties should only read information from the screen (a
video-frame or screenshot). They should not interact with the device-under-test,
nor read information from the network. This <em>device_profile</em> section shouldn’t
contain details about a single device (such as the device’s IP address) — that
would belong in the <em>device_under_test</em> section. Avoiding high-cardinality
values (IP addresess, serial numbers) in the <em>device_profile</em> section will help
the Object Repository generate its data more efficiently, too.</p>

<p>Optionally, use the new <a href="https://stb-tester.com/manual/configuration#test-pack-node-label-format">test_pack.node_label_format</a> setting in your global
<a href="https://stb-tester.com/manual/configuration#stbt-conf">.stbt.conf</a> file to display the device type (or any other device-specific
configuration) in the Stb-tester Portal web interface.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>How to configure log capture with Stb-tester</title>
        <pubDate>Tue, 17 Dec 2024 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2024/12/17/log-capture</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2024/12/17/log-capture</guid>
        <description>
          &lt;p&gt;Stb-tester can capture logs from the device-under-test (such as ADB logs from an
Android device) and display them alongside the logs from the Python test script
itself. As always, the timestamps in the log are accurately &lt;a href=&quot;https://stb-tester.com/manual/log-viewer&quot;&gt;synchronised&lt;/a&gt; with
the video-recording of the automated test-run or &lt;a href=&quot;https://stb-tester.com/blog/2024/12/10/manual-recording&quot;&gt;manual testing session&lt;/a&gt;.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester can capture logs from the device-under-test (such as ADB logs from an
Android device) and display them alongside the logs from the Python test script
itself. As always, the timestamps in the log are accurately <a href="https://stb-tester.com/manual/log-viewer">synchronised</a> with
the video-recording of the automated test-run or <a href="https://stb-tester.com/blog/2024/12/10/manual-recording">manual testing session</a>.</p>

<figure class="m-b-2">
  <img src="https://stb-tester.com/blog/images/log-capture/android-logs.png" class="border" alt="Screenshot of Stb-tester's log viewer web interface" />
  <figcaption>
  Stb-tester's <a href="https://stb-tester.com/manual/log-viewer">interactive log viewer</a>
  with video recording and ADB logs
  </figcaption>
</figure>

<p>We recommend using a pytest <strong>fixture</strong> to start and stop log capture.
Pytest is the test-runner used by Stb-tester <a href="https://stb-tester.com/blog/2022/11/03/v33-pytest">since v33</a>; a fixture allows you
to run code before and after every test-case. (See
<a href="https://docs.pytest.org/en/stable/how-to/fixtures.html">pytest’s documentation on fixtures</a>.)</p>

<p>For detailed instructions see the new chapter in the Stb-tester manual:
<a href="https://stb-tester.com/kb/logs">Capturing logs from the device-under-test</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Feature announcement: Record video of manual testing sessions
</title>
        <pubDate>Tue, 10 Dec 2024 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2024/12/10/manual-recording</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2024/12/10/manual-recording</guid>
        <description>
          &lt;p&gt;You can now record a video of a manual testing session, including all the logs
from the device-under-test that you usually expect when using Stb-tester.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>You can now record a video of a manual testing session, including all the logs
from the device-under-test that you usually expect when using Stb-tester.</p>

<figure class="text-center">
  <video style="width: 700px; height: 525px;" src="https://stb-tester.com/blog/images/manual-recording/manual-recording.mp4" class="border screenshot" controls=""></video>
  <figcaption>
    <p>Demo of new feature</p>
  </figcaption>
</figure>

<p>Simply click the “Record video” button underneath the live video on the “Manual
control” tab of the Stb-tester Portal.</p>

<p>Once it has started recording you can use the remote control as usual. When you
are done, click “Stop Recording”, and it will provide a link which you can
copy-paste into whichever system you’re using to manage your manual tests.</p>

<p>The link includes the video recording and the logs from the set-top-box itself,
which are synchronised with the video. To configure log capture see
<a href="https://stb-tester.com/kb/logs">Configuration Reference: Capturing logs from the device-under-test</a>
in the Stb-tester manual.</p>

<p>This new feature has been highly requested by our users. It serves a dual
purpose:</p>

<ul>
  <li>Easily capturing and sharing video recordings of UAT/discovery sessions:
helps product owners to understand user behaviour and identify areas for
optimisation.</li>
  <li>Automatically capturing ADB logs during manual testing sessions: saves
testers’ time and enhances the testing experience.</li>
</ul>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Object Repository performance improvements</title>
        <pubDate>Thu, 20 Jun 2024 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2024/06/20/faster-object-repo</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2024/06/20/faster-object-repo</guid>
        <description>
          &lt;p&gt;Stb-tester’s &lt;a href=&quot;https://stb-tester.com/manual/object-repository&quot;&gt;Object Repository&lt;/a&gt; is a tool for faster development &amp;amp; debugging
of your “Page Object” Python code. Today we have released significant
performance &amp;amp; usability improvements to make this tool better suited for very
large code-bases.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester’s <a href="https://stb-tester.com/manual/object-repository">Object Repository</a> is a tool for faster development &amp; debugging
of your “Page Object” Python code. Today we have released significant
performance &amp; usability improvements to make this tool better suited for very
large code-bases.</p>

<figure>
  <p><img src="https://stb-tester.com/blog/images/object-repo/Processing.png" class="border" /></p>
</figure>

<h2 id="background">Background</h2>

<p>The Object Repository works by checking your Python code against canned
screenshots of the device-under-test, instead of a live video feed. This
provides a much faster turn-around time for your developers’ edit-test-debug
cycle when writing or modifying your “Page Object” Python code. But if you had
many of these canned screenshots (Stb-tester calls them “selftest screenshots”)
the cycle was still too slow.</p>

<p>The Object Repository checks your Page Object code against each screenshot by
running the code on an Stb-tester Node. This ensures that the behaviour is the
same as when you run the full test scripts, and it simplifies deployment of the
Object Repository tool (the code runs in the same <a href="https://stb-tester.com/kb/custom-environment">Python environment</a>, with
the same Python libraries available, and the same network environment, as your
real test scripts).</p>

<h2 id="the-performance-improvements">The performance improvements</h2>

<p>Our new performance improvements come from the following changes:</p>

<ol>
  <li>
    <p>The Object Repository only checks the screenshots that you select, on
demand. (Previously, it would check your selected Page Object against <em>all</em>
the selftest screenshots in your test-pack, and then show you the data for
the selected ones.)</p>
  </li>
  <li>
    <p>By default, the Object Repository selects the screenshots in
<code class="language-plaintext highlighter-rouge">selftest/screenshots/{appname}/{pagename}/**.png</code>.<br />
To check other screenshots, click the “Select Screenshots” button.</p>
  </li>
  <li>
    <p>The work of checking each screenshot is farmed out to all idle Stb-tester
Nodes, in parallel. (Previously, all screenshots were processed on a single
Node.)</p>
  </li>
</ol>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Celebrating 10 years of Stb-tester</title>
        <pubDate>Mon, 04 Mar 2024 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2024/03/04/celebrating-10-years-of-stb-tester</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2024/03/04/celebrating-10-years-of-stb-tester</guid>
        <description>
          Last December our company turned 10 years old. Since founding our company 10
years ago, we have grown from a services-focused company in the first 2 years,
to a primarily product-based company since 2016 with uninterrupted year-on-year
sales growth and customers in 4 continents. In 2023 we had our best year yet,
with sales up 50% compared to the previous year, and headcount up 25%.

        </description>
        <content:encoded><![CDATA[
          <p>Last December our company turned 10 years old (and we had started the
Stb-tester open-source project at <a href="https://www.youview.com/">YouView</a> 18 months before that).</p>

<figure class="pull-right border" style="width:40%">
  <img src="https://stb-tester.com/blog/images/10-years/Stb-tester active licenses.png" alt="Stb-tester Node active licenses, by year" />
</figure>

<p>Since founding our company 10 years ago, we have grown from a services-focused
company in the first 2 years, to a primarily product-based company since 2016
with uninterrupted year-on-year sales growth and customers in 4 continents. In
2023 we had our best year yet, with sales up 50% compared to the previous year,
and headcount up 25%. Now barely one quarter into 2024, sales are already on
trajectory to exceed 2023’s growth, all the while we continue to release new
features such as our latest “<a href="https://stb-tester.com/blog/2024/02/29/smart-navigation">Smart Navigation</a>” machine learning APIs.</p>

<div class="main-timeline">
  <ul>
    <li>
      <dl>
        <dt>2012</dt>
        <dd>Started the Stb-tester open source project at <a href="https://www.youview.com/">YouView</a>.</dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2013</dt>
        <dd>Presented Stb-tester at the <a href="https://www.youtube.com/watch?v=Fdn2LxxM7wA&amp;list=SPSIUOFhnxEiCODb8XQB-RUQ0RGNZ2yW7d">Google Test Automation Conference</a> in
New York.

          <p>Founded Stb-tester.com Ltd.</p>
        </dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2014</dt>
        <dd><img src="https://stb-tester.com/blog/images/10-years/stb-tester-one.jpg" style="float:left; width:30%; margin-right:10px" />

          <p>First sales of our first product, the “Stb-tester <small>ONE</small>”
hardware appliance.</p>
        </dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2016</dt>
        <dd>Revenue from product sales overtook revenue from services work.</dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2017</dt>
        <dd><img src="https://stb-tester.com/blog/images/10-years/stb-tester-hdmi-node.jpg" style="float:left; width:40%; margin-right:10px;" />

          <p>Launched the <a href="https://stb-tester.com/blog/2018/01/17/announcing-the-stb-tester-cloud-platform">Stb-tester Cloud portal</a> + new “Stb-tester Node” hardware —
enabling centralised access to test hardware spread across any number of
physical locations, with slingbox-style video streaming and remote control
from any web browser, 5 years before our competitors.</p>

          <p>With this architecture we truly found our “product-market fit” and the sales
data shows it!</p>
        </dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2018</dt>
        <dd>Sales of the Stb-tester Node continue to grow rapidly; we have customers
in 4 continents.

          <p>Released new audio-testing APIs and groundbreaking “Object Repository” tool
for faster test development &amp; maintenance.</p>
        </dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2019</dt>
        <dd>Technical improvements such as Python 3 support, IDE integration, and (as
always) we continued to release new &amp; improved image-processing APIs for
your test scripts.</dd>
      </dl>
    </li>
    <li class="timeline-double-year">
      <dl>
        <dt>2020-2021</dt>
        <dd>Our sales continued stable (and even grew slightly) throughout the
pandemic, thanks in part to our slingbox-style capabilities that enable
work-from-home testing.</dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2022</dt>
        <dd><img src="https://stb-tester.com/blog/images/10-years/stb-tester-node-2nd-gen.jpg" style="float:left; width:40%; margin-right:10px;" />

          <p>We released our “second generation” Stb-tester Node hardware — fanless
(totally silent), with a bump to CPU/GPU/RAM specs, and improved supply
chain guarantees after weathering the chip shortage of 2021.</p>
        </dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2023</dt>
        <dd>Our best year yet, with sales up almost 50% compared to the previous year
and headcount up 25%.

          <p>Scaled our cloud portal architecture to support hundreds of simultaneous
Stb-tester Nodes per installation.</p>

          <p>Behind the scenes, a lot of R&amp;D was focused on features you’ll be seeing in
2024…</p>
        </dd>
      </dl>
    </li>
    <li>
      <dl>
        <dt>2024</dt>
        <dd>The first quarter isn’t over, and we have already released our new
“<a href="https://stb-tester.com/blog/2024/02/29/smart-navigation">Smart Navigation</a>” machine learning APIs, with many more AI features and
“codeless” editing to come!</dd>
      </dl>
    </li>
  </ul>
</div>

<h2 id="backward-compatibility">Backward compatibility</h2>
<p>Throughout all our new hardware models, architecture changes, and new APIs, we
have maintained compatibility with the test scripts you have written with our
older hardware &amp; APIs. Backward compatibility is one of our core company
values! We respect the time and effort you have invested into developing your
test scripts. We have customers who started with our first “Stb-tester
<small>ONE</small>” hardware appliance, migrated from the single-appliance model
to our current cloud portal with distributed “Stb-tester Node” hardware, and are
still running the same test scripts.</p>

<h2 id="thank-you">Thank you</h2>

<p>We are very thankful to <a href="https://www.youview.com/">YouView</a> who supported the original development of
Stb-tester, and to you, our customers, for your business and feedback
throughout the years. Here’s to another 10 years of mutually fruitful
partnership!</p>

<h2 id="about-stb-tester">About Stb-tester</h2>

<p>Stb-tester is an automation tool for testing TV &amp; video-streaming apps on real
devices such as Android TV, Apple TV, Fire TV, PlayStation, Roku, XBox, and
traditional Set-Top Boxes.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester&apos;s new &quot;Smart Navigation&quot; functions</title>
        <pubDate>Thu, 29 Feb 2024 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2024/02/29/smart-navigation</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2024/02/29/smart-navigation</guid>
        <description>
          &lt;p&gt;Today we have released Stb-tester’s new “Smart Navigation” functions
&lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.navigate_1d&quot;&gt;stbt.navigate_1d&lt;/a&gt;&lt;/em&gt; and &lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.navigate_grid&quot;&gt;stbt.navigate_grid&lt;/a&gt;&lt;/em&gt;. These functions will
autonomously navigate your application’s UI, learning the structure of your
menus to speed up future invocations. These are the first of many Machine
Learning / AI features on Stb-tester’s roadmap; we think you’ll agree that these
new APIs will greatly simplify and speed up your test development.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Today we have released Stb-tester’s new “Smart Navigation” functions
<em><a href="https://stb-tester.com/manual/python-api#stbt.navigate_1d">stbt.navigate_1d</a></em> and <em><a href="https://stb-tester.com/manual/python-api#stbt.navigate_grid">stbt.navigate_grid</a></em>. These functions will
autonomously navigate your application’s UI, learning the structure of your
menus to speed up future invocations. These are the first of many Machine
Learning / AI features on Stb-tester’s roadmap; we think you’ll agree that these
new APIs will greatly simplify and speed up your test development.</p>

<p>Key features:</p>

<ul>
  <li><strong>Easy to use:</strong> There’s very little code for the user (you!) to write.</li>
  <li><strong>Widely applicable:</strong> It’s designed to handle all sorts of menus — lists vs.
grids, wrapping vs. non-wrapping, fixed focus vs. moving focus, etc.  To
customise it for your UI, you only need to provide a Page Object that reads
the name of the currently focused item.</li>
  <li><strong>Robust:</strong> Even if the real menu has changed from what we learned the
previous time, it will detect this and learn the new structure.</li>
</ul>

<p>See this screencast for a demo and a thorough explanation:</p>

<video controls="" src="https://stb-tester.com/blog/images/navigation/Stb-tester Smart Navigation.mp4"></video>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Controlling Apple TV with pyatv</title>
        <pubDate>Thu, 25 Jan 2024 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2024/01/25/appletv-pyatv</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2024/01/25/appletv-pyatv</guid>
        <description>
          &lt;p&gt;&lt;a href=&quot;https://pyatv.dev/&quot;&gt;pyatv&lt;/a&gt; is an open-source tool (MIT license) for controlling Apple TV devices
over the network. It uses AirPlay and other network protocols supported by the
Apple TV. The most relevant features for us are: Listing the installed apps,
launching an app, and sending text to the on-screen keyboard.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p><a href="https://pyatv.dev/">pyatv</a> is an open-source tool (MIT license) for controlling Apple TV devices
over the network. It uses AirPlay and other network protocols supported by the
Apple TV. The most relevant features for us are: Listing the installed apps,
launching an app, and sending text to the on-screen keyboard.</p>

<p>These were always possible to do with Stb-tester, by navigating the relevant
AppleTV screens, but pyatv is much faster because it doesn’t require navigation:</p>

<table class="table table-condensed">
  <thead>
    <tr>
      <th>Action</th>
      <th>Stb-tester</th>
      <th>pyatv</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Launch an app</td>
      <td>Navigate the AppleTV home grid</td>
      <td>Send the command over the network</td>
    </tr>
    <tr>
      <td>Enter text</td>
      <td>Navigate the on-screen keyboard</td>
      <td>Send the entire text over the network</td>
    </tr>
    <tr>
      <td> </td>
      <td><video class="screenshot" controls="" autoplay="" muted="true"><source src="https://stb-tester.com/blog/images/pyatv/with_navigation.mp4" /></video></td>
      <td><video class="screenshot" controls="" autoplay="" muted="true"><source src="https://stb-tester.com/blog/images/pyatv/with_pyatv.mp4" /></video></td>
    </tr>
  </tbody>
</table>

<p>For usage, configuration and installation instructions, see <a href="https://stb-tester.com/manual/pyatv">Controlling Apple
TV with pyatv</a> in the Stb-tester manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester adds support for TP-Link &quot;Kasa&quot; smart plugs</title>
        <pubDate>Wed, 19 Jul 2023 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2023/07/19/kasa-smart-plugs</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2023/07/19/kasa-smart-plugs</guid>
        <description>
          &lt;p&gt;Stb-tester now supports the TP-Link “Kasa” brand of smart plugs. This rounds
out Stb-tester’s already impressive support for a wide range of
network-controlled Power Distribution Unit (PDU) models from ATEN, Aviosys,
Lindy, PDUeX, and Rittal, which allows you to power-cycle the device-under-test
from your test scripts.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester now supports the TP-Link “Kasa” brand of smart plugs. This rounds
out Stb-tester’s already impressive support for a wide range of
network-controlled Power Distribution Unit (PDU) models from ATEN, Aviosys,
Lindy, PDUeX, and Rittal, which allows you to power-cycle the device-under-test
from your test scripts.</p>

<table class="table">
  <thead>
    <tr>
      <th>PDU</th>
      <th>Ports</th>
      <th>Rack mount?</th>
      <th>Network connection</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://www.aten.com/us/en/products/power-distribution-&amp;-racks/rack-pdu/pe6108/">ATEN PE6108</a></td>
      <td>8</td>
      <td>✓</td>
      <td>Ethernet</td>
    </tr>
    <tr>
      <td><a href="https://www.lindy.co.uk/networking-c5/ipower-switch-classic-8-p4456">Lindy IPower Switch</a></td>
      <td>8</td>
      <td>✓</td>
      <td>Ethernet</td>
    </tr>
    <tr>
      <td><a href="https://www.pduexperts.com/product-category/intelligent-power-distribution/switch-pdu/">PDUeX KWX</a></td>
      <td>8</td>
      <td>✓</td>
      <td>Ethernet</td>
    </tr>
    <tr>
      <td><a href="https://www.rittal.com/com-en/products/PG0229STV1/PG7274STV1/PGR11260STV1/PRO115365?variantId=7979302">Rittal DK 7955.310</a></td>
      <td>6-18</td>
      <td>✓</td>
      <td>Ethernet</td>
    </tr>
    <tr>
      <td><a href="https://aviosys.com/products/9255pro.php">Aviosys IP Power</a></td>
      <td>1-8</td>
      <td> </td>
      <td>Ethernet</td>
    </tr>
    <tr>
      <td><a href="https://www.tp-link.com/en/home-networking/smart-plug/?filterby=5847">TP-Link Kasa</a></td>
      <td>1</td>
      <td> </td>
      <td>WiFi</td>
    </tr>
  </tbody>
</table>

<p>For more details, including configuration instructions and API reference
documentation, see <a href="https://stb-tester.com/manual/pdu">Power Distribution Units</a> in the Stb-tester manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester v34 released: Better motion detection, pytest, and more
</title>
        <pubDate>Wed, 14 Jun 2023 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2023/06/14/stb-tester-v34-released</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2023/06/14/stb-tester-v34-released</guid>
        <description>
          &lt;p&gt;Version 34 of Stb-tester’s “stbt” Python API is now available. This release
makes important improvements to the motion-detection algorithm of
&lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.wait_for_motion&quot;&gt;stbt.wait_for_motion&lt;/a&gt;&lt;/em&gt; and &lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.press_and_wait&quot;&gt;stbt.press_and_wait&lt;/a&gt;&lt;/em&gt;, born from our personal
experience of writing &amp;amp; maintaining tests for Apple TV, DirecTV, Fire TV, Sky,
and Xfinity devices over the last 4 years.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Version 34 of Stb-tester’s “stbt” Python API is now available. This release
makes important improvements to the motion-detection algorithm of
<em><a href="https://stb-tester.com/manual/python-api#stbt.wait_for_motion">stbt.wait_for_motion</a></em> and <em><a href="https://stb-tester.com/manual/python-api#stbt.press_and_wait">stbt.press_and_wait</a></em>, born from our personal
experience of writing &amp; maintaining tests for Apple TV, DirecTV, Fire TV, Sky,
and Xfinity devices over the last 4 years.</p>

<p>This release also enables <strong><a href="https://pytest.org/">Pytest</a></strong> by default. Pytest is a popular Python
test-runner that provides advanced functionality such as <strong>assertion
introspection</strong>, and <strong>fixtures</strong> for test setup &amp; teardown. Pytest support was
added in Stb-tester v33, but it was disabled by default; in v34 it is the
default (and only) test-runner. Our largest customers have been using the
Pytest support with v33 for at least 6 months, so we think it’s time that all
our users benefit from Pytest’s features. You shouldn’t see any change in
behaviour from your tests, except that some of the output in stbt.log will be
different (with additional, useful information). To learn more about Pytest see
<a href="https://stb-tester.com/blog/2022/11/03/v33-pytest">New features in v33: Pytest support</a>.</p>

<p>This release includes several other minor additions and fixes. For full
details see the <a href="https://stb-tester.com/manual/release-notes">release notes</a>.</p>

<div class="bs-callout bs-callout-default">

  <h4 id="note">Note:</h4>

  <p>Changes to the <em>stbt</em> core Python API, and the test-run environment, are
version-controlled. You can specify the version you want to use in your
<code class="language-plaintext highlighter-rouge">.stbt.conf</code> file. This mechanism allows you to upgrade in a controlled manner,
and to test the upgrade on a branch first.</p>

</div>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New features in v33: Using segmentation to find GUI elements
</title>
        <pubDate>Fri, 11 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2022/11/11/v33-segmentation</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2022/11/11/v33-segmentation</guid>
        <description>
          &lt;p&gt;In image processing, “segmentation” means finding which pixels belong to
foreground objects (such as text or other GUI elements) versus background
pixels. Stb-tester v33 adds a new API called &lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.segment&quot;&gt;segment&lt;/a&gt; (pronounced like the
verb: segMENT) that finds the location of distinct foreground elements.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>In image processing, “segmentation” means finding which pixels belong to
foreground objects (such as text or other GUI elements) versus background
pixels. Stb-tester v33 adds a new API called <a href="https://stb-tester.com/manual/python-api#stbt.segment">segment</a> (pronounced like the
verb: segMENT) that finds the location of distinct foreground elements.</p>

<p>For example, let’s find the location of each “poster” or “tile” in this
screenshot:</p>

<figure>
  <p><img src="https://stb-tester.com/blog/images/segment/xfinity-keyboard.png" /></p>
</figure>

<p>By default, <a href="https://stb-tester.com/manual/python-api#stbt.segment">segment</a> starts from the top of the screen and travels down,
finding distinct rows:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">frame</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">get_frame</span><span class="p">()</span>
<span class="n">regions</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">segment</span><span class="p">(</span><span class="n">frame</span><span class="p">)</span>
</code></pre></div></div>

<figure id="screenshot1">
<img src="https://stb-tester.com/blog/images/segment/xfinity-keyboard.png" />
<canvas width="1280" height="720"></canvas>
</figure>

<p>Next, we can discard rows that are too small, and then run <a href="https://stb-tester.com/manual/python-api#stbt.segment">segment</a> again, but
horizontally: Starting from the left of the row, and moving right.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tiles</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">regions</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">row</span><span class="p">.</span><span class="n">height</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="p">:</span>
        <span class="n">tiles</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="nf">segment</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">region</span><span class="o">=</span><span class="n">row</span><span class="p">,</span>
                                  <span class="n">initial_direction</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="n">Direction</span><span class="p">.</span><span class="n">HORIZONTAL</span><span class="p">))</span>
</code></pre></div></div>

<figure id="screenshot2">
<img src="https://stb-tester.com/blog/images/segment/xfinity-keyboard.png" />
<canvas width="1280" height="720"></canvas>
</figure>

<p>We can combine both of these steps into a single call to <a href="https://stb-tester.com/manual/python-api#stbt.segment">segment</a>, by
specifying <code class="language-plaintext highlighter-rouge">steps=2</code>. The steps alternate between vertical and horizontal
directions:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">regions</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">segment</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">steps</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div></div>

<figure id="screenshot3">
<img src="https://stb-tester.com/blog/images/segment/xfinity-keyboard.png" />
<canvas width="1280" height="720"></canvas>
</figure>

<p>Then we can discard regions that are too small to be a tile:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tiles</span> <span class="o">=</span> <span class="p">[</span><span class="n">r</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">segment</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">steps</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
         <span class="k">if</span> <span class="n">r</span><span class="p">.</span><span class="n">height</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="p">]</span>
</code></pre></div></div>

<figure id="screenshot4">
<img src="https://stb-tester.com/blog/images/segment/xfinity-keyboard.png" />
<canvas width="1280" height="720"></canvas>
</figure>

<p>Alternately, we could have limited the search region —if we know it— by
specifying the <code class="language-plaintext highlighter-rouge">region</code> parameter of <a href="https://stb-tester.com/manual/python-api#stbt.segment">segment</a>, instead of searching the whole
frame.</p>

<p>Note that some of the regions aren’t the same height, because the bottom of
the poster is too similar to the background color. If we set <code class="language-plaintext highlighter-rouge">narrow=False</code>,
then <a href="https://stb-tester.com/manual/python-api#stbt.segment">segment</a> will keep the top and bottom coordinates of the row from the
previous step:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tiles</span> <span class="o">=</span> <span class="p">[</span><span class="n">r</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">segment</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">steps</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">narrow</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
         <span class="k">if</span> <span class="n">r</span><span class="p">.</span><span class="n">height</span> <span class="o">&gt;</span> <span class="mi">100</span> <span class="ow">and</span> <span class="n">r</span><span class="p">.</span><span class="n">width</span> <span class="o">&gt;</span> <span class="mi">40</span><span class="p">]</span>
</code></pre></div></div>

<figure id="screenshot5">
<img src="https://stb-tester.com/blog/images/segment/xfinity-keyboard.png" />
<canvas width="1280" height="720"></canvas>
</figure>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New features in v33: Creating masks programmatically
</title>
        <pubDate>Thu, 10 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2022/11/10/v33-masks</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2022/11/10/v33-masks</guid>
        <description>
          &lt;p&gt;Stb-tester v33 introduces a new, easier, way of defining masks for
image-processing APIs such as &lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.is_screen_black&quot;&gt;is_screen_black&lt;/a&gt;, &lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.press_and_wait&quot;&gt;press_and_wait&lt;/a&gt;, and
&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.wait_for_motion&quot;&gt;wait_for_motion&lt;/a&gt;.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester v33 introduces a new, easier, way of defining masks for
image-processing APIs such as <a href="https://stb-tester.com/manual/python-api#stbt.is_screen_black">is_screen_black</a>, <a href="https://stb-tester.com/manual/python-api#stbt.press_and_wait">press_and_wait</a>, and
<a href="https://stb-tester.com/manual/python-api#stbt.wait_for_motion">wait_for_motion</a>.</p>

<p>Previously, these APIs could take a single rectangular <a href="https://stb-tester.com/manual/python-api#stbt.Region">Region</a> to restrict the
image-processing to that part of the screen. If you needed to process an area
that isn’t a rectangle —for example processing the whole screen <em>except</em> for
some parts— then you had to create a black-and-white mask in an image editor:</p>

<table class="table table-condensed">
  <thead>
    <tr>
      <th>frame</th>
      <th>mask</th>
      <th>is_screen_black(frame, mask)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><img src="https://stb-tester.com/blog/images/masks/frame.jpg" alt="" /></td>
      <td>None</td>
      <td>False</td>
    </tr>
    <tr>
      <td><img src="https://stb-tester.com/blog/images/masks/frame.jpg" alt="" /></td>
      <td><img src="https://stb-tester.com/blog/images/masks/mask.png" alt="" /></td>
      <td>True</td>
    </tr>
  </tbody>
</table>

<p>Since v33, you can construct such a mask in Python code by adding or
subtracting regions. For example, the mask shown above can be created with the
following code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">channel_patch</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">570</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">264</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">715</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">450</span><span class="p">)</span>
<span class="n">info_bar</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">520</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">1280</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">690</span><span class="p">)</span>
<span class="n">mask</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="n">Region</span><span class="p">.</span><span class="n">ALL</span> <span class="o">-</span> <span class="n">channel_patch</span> <span class="o">-</span> <span class="n">info_bar</span>
</code></pre></div></div>

<p>To learn more, see <a href="https://stb-tester.com/manual/masks">Regions and Masks</a> in the Stb-tester manual.</p>

<p>v33 includes many other improvements, including an upgrade to Python 3.10, APIs
for interacting with Android and Roku devices, and more. See the <a href="https://stb-tester.com/manual/release-notes">release notes</a>
for instructions on how to upgrade.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New features in v33: Finding elements by color
</title>
        <pubDate>Wed, 09 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2022/11/09/v33-find-by-color</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2022/11/09/v33-find-by-color</guid>
        <description>
          &lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.find_regions_by_color&quot;&gt;find_regions_by_color&lt;/a&gt;&lt;/em&gt; is a new API that finds contiguous regions of a
particular color. It can be used to identify GUI elements such as the
“selection” or “focus” on menus and carousels.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p><em><a href="https://stb-tester.com/manual/python-api#stbt.find_regions_by_color">find_regions_by_color</a></em> is a new API that finds contiguous regions of a
particular color. It can be used to identify GUI elements such as the
“selection” or “focus” on menus and carousels.</p>

<p>For example, let’s find the current focus in the menu at the top of this
screenshot:</p>

<figure>
  <p><img src="https://stb-tester.com/blog/images/find-regions-by-color/football.png" /></p>
</figure>

<p>We’ll match the logo in the top-left corner to check if the menu is present,
and we’ll use <em>find_regions_by_color</em> to find the position of the white
underline or “focus”:</p>

<figure>
  <p><img src="https://stb-tester.com/blog/images/find-regions-by-color/detail.png" /></p>
</figure>

<p>Here’s a <a href="https://stb-tester.com/manual/object-repository#what-is-a-page-object">Page Object</a> that implements this:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">stbt</span>

<span class="k">class</span> <span class="nc">Menu</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">is_visible</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="nf">bool </span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">"BT SPORT.png"</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">)</span>
                     <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">underline</span><span class="p">)</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">underline</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">r</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">find_regions_by_color</span><span class="p">(</span>
            <span class="s">"#fff"</span><span class="p">,</span>
            <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">,</span>
            <span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">1280</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">90</span><span class="p">),</span>
            <span class="n">min_size</span><span class="o">=</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">r</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">r</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
</code></pre></div></div>

<p><em>find_regions_by_color</em> takes a color in either of the following formats:</p>

<ul>
  <li>A string in the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color">CSS hexadecimal color format</a>: White is <code class="language-plaintext highlighter-rouge">"#ffffff"</code> or
<code class="language-plaintext highlighter-rouge">"#fff"</code>.</li>
  <li>A tuple of 3 numbers in Blue, Green, Red order (note that this is the
opposite of the CSS Red, Green, Blue order). White is <code class="language-plaintext highlighter-rouge">(255, 255, 255)</code>.</li>
</ul>

<p>It returns a <em>list</em> of regions. Here we are looking in the narrow band
where we expect the white underline to be, so we only expect to find 1 region.
If we find any more or any less, it’s probably a different page so we make
<em>is_visible</em> return <em>False</em>.</p>

<p>Now we can use <a href="https://stb-tester.com/manual/python-api#stbt.ocr">ocr</a> to read the text above the underline:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Menu</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="p">...</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">focus</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span>
            <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">,</span>
            <span class="n">region</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">underline</span>
                       <span class="p">.</span><span class="nf">above</span><span class="p">()</span>
                       <span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">x</span><span class="o">=-</span><span class="mi">10</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=-</span><span class="mi">5</span><span class="p">))</span>
</code></pre></div></div>

<p>We can test our Page Object in the <a href="https://stb-tester.com/manual/object-repository">Object Repository</a> (simulated below). You
can see that for each example screenshot, our <em>underline</em> property is finding
the correct position (try hovering over the <em>stbt.Region</em> text in the table)
and our <em>focus</em> property is reading the correct menu entry.</p>

<div class="objectrepo">
  <h3>Menu</h3>
  <table class="table table-condensed">
    <tbody>
    <tr>
      <td></td>
      <td>
        <div class="screenshot">
          <div class="embed-responsive embed-responsive-16by9">
            <img src="https://stb-tester.com/blog/images/find-regions-by-color/channels.png" class="embed-responsive-item" />
          </div>
          <canvas width="1280" height="720"></canvas>
        </div>
        <a href="https://stb-tester.com/blog/images/find-regions-by-color/channels.png" class="text-muted">channels.png</a>
      </td>
      <td>
        <div class="screenshot">
          <div class="embed-responsive embed-responsive-16by9">
            <img src="https://stb-tester.com/blog/images/find-regions-by-color/football.png" class="embed-responsive-item" />
          </div>
          <canvas width="1280" height="720"></canvas>
        </div>
        <a href="https://stb-tester.com/blog/images/find-regions-by-color/football.png" class="text-muted">football.png</a>
      </td>
    </tr>
    <tr>
      <td class="code">x</td>
      <td class="field type">&lt;Menu&gt;</td>
      <td class="field type">&lt;Menu&gt;</td>
    </tr>
    <tr>
      <td class="code">x.is_visible</td>
      <td class="field bool">True</td>
      <td class="field bool">True</td>
    </tr>
    <tr>
      <td class="code">x.focus</td>
      <td class="field str" onmouseenter="highlight(1, [195, 0, 295, 78])" onmouseleave="unhighlight(1)">Channels</td>
      <td class="field str" onmouseenter="highlight(2, [318, 0, 407, 78])" onmouseleave="unhighlight(2)">Football</td>
    </tr>
    <tr>
      <td class="code">x.underline</td>
      <td class="field region" onmouseenter="highlight(1, [205, 83, 285, 85])" onmouseleave="unhighlight(1)">
        stbt.Region(x=205, y=83, right=285, bottom=85)
      </td>
      <td class="field region" onmouseenter="highlight(2, [323, 83, 397, 85])" onmouseleave="unhighlight(2)">
          stbt.Region(x=323, y=83, right=397, bottom=85)
      </td>
    </tr>
    </tbody>
  </table>
</div>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New features in v33: BDD support
</title>
        <pubDate>Tue, 08 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2022/11/08/v33-bdd</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2022/11/08/v33-bdd</guid>
        <description>
          &lt;p&gt;With the &lt;a href=&quot;https://stb-tester.com/blog/2022/11/03/v33-pytest&quot;&gt;pytest support&lt;/a&gt; introduced in Stb-tester v33, you can use
&lt;a href=&quot;https://pytest-bdd.readthedocs.io/en/6.1.1/&quot;&gt;pytest-bdd&lt;/a&gt; to run Stb-tester from BDD specifications (also known as
“Gherkin” syntax, or “Given/When/Then” acceptance criteria).&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>With the <a href="https://stb-tester.com/blog/2022/11/03/v33-pytest">pytest support</a> introduced in Stb-tester v33, you can use
<a href="https://pytest-bdd.readthedocs.io/en/6.1.1/">pytest-bdd</a> to run Stb-tester from BDD specifications (also known as
“Gherkin” syntax, or “Given/When/Then” acceptance criteria).</p>

<div class="bs-callout bs-callout-warning">
  <h4>
  <span class="glyphicon glyphicon-warning-sign"></span>
  We don’t endorse or recommend BDD for Stb-tester tests
</h4>

  <p>But if you really want or need to, now you can.</p>

  <p>I recommend thinking about ways to reduce boilerplate in the “step definitions”
code. In BDD, you have to write code that converts each natural-language “step”
in the scenario, to actual Python code that carries out the test. Ideally you
could write a small translation layer that automatically converts the scenario
steps to <a href="https://stb-tester.com/manual/object-repository#what-is-a-page-object">Page Object</a> method calls (without having to write “step definitions”
manually for every different Page Object class &amp; method).</p>

</div>

<p>Here’s a quick example showing one Gherkin “feature” with two “scenarios” — one
of them is a “scenario outline” to be run with different parameters or
“examples”. They are marked with tags like “@menu” and “@nav”, to enable
selecting or deselecting the scenarios to run. The BDD scenarios are on the
left, with the Python step definitions on the right. For more details see the
<a href="https://pytest-bdd.readthedocs.io/en/6.1.1/">pytest-bdd</a> documentation.</p>

<figure class="screenshot">
  <p><img src="https://stb-tester.com/blog/images/bdd/code.png" alt="" /></p>
</figure>

<h3 id="configuration">Configuration</h3>

<div class="bs-callout bs-callout-warning">
  <h4>
  <span class="glyphicon glyphicon-warning-sign"></span>
  Update for Stb-tester v34
</h4>
  <p>As of Stb-tester v34 no additional configuration is required to enable BDD.</p>
</div>

<p>You can pass additional command-line options to pytest-bdd in your <code class="language-plaintext highlighter-rouge">.stbt.conf</code>
file like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[test_pack]
stbt_version = 33
pytest = --cucumberjson=steps.json
</code></pre></div></div>

<p>For each test-run, if an artifact called “steps.json” exists, the Stb-tester
Portal will show the detailed pass/fail status of each step:</p>

<figure class="screenshot">
  <p><img src="https://stb-tester.com/blog/images/bdd/result.png" alt="" /></p>
</figure>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New features in v33: Pytest support
</title>
        <pubDate>Thu, 03 Nov 2022 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2022/11/03/v33-pytest</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2022/11/03/v33-pytest</guid>
        <description>
          &lt;p&gt;Stb-tester v33 supports running your tests with &lt;strong&gt;&lt;a href=&quot;https://pytest.org&quot;&gt;pytest&lt;/a&gt;&lt;/strong&gt;, a popular Python
test-runner. Pytest provides powerful functionality such as &lt;strong&gt;assertion
introspection&lt;/strong&gt; and &lt;strong&gt;fixtures&lt;/strong&gt;.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester v33 supports running your tests with <strong><a href="https://pytest.org">pytest</a></strong>, a popular Python
test-runner. Pytest provides powerful functionality such as <strong>assertion
introspection</strong> and <strong>fixtures</strong>.</p>

<h3 id="assertion-introspection">Assertion introspection</h3>

<p>When an <strong>assert</strong> in your test fails, pytest prints additional information
about the value that triggered the assertion. Without pytest the output looks
like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    assert menu.selection == "Channels"
AssertionError
</code></pre></div></div>

<p>Pytest’s output looks like this:</p>

<div class="highlight">
  <pre><code>&gt;       <span style="color:#0000FF;">assert</span> menu.selection == <span style="color:#FFC706;">"Channels"</span>
<span style="font-weight:bold;color:#DE382B;">E       AssertionError: assert 'Settings' == 'Channels'</span>
<span style="font-weight:bold;color:#DE382B;">E         - Channels</span>
<span style="font-weight:bold;color:#DE382B;">E         + Settings</span>
<span style="font-weight:bold;color:#DE382B;">tests/btsport/demo.py</span>:13: AssertionError
</code></pre>
</div>

<p>Much more helpful!</p>

<p>To learn more, see <a href="https://docs.pytest.org/en/6.2.x/assert.html">pytest’s documentation about asserting</a>.</p>

<h3 id="fixtures">Fixtures</h3>

<p>Pytest’s fixtures allow you to define setup and teardown code that can be
re-used in many tests. For example, the following fixture captures logs from
the device-under-test while the test is running:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@pytest.fixture</span>
<span class="k">def</span> <span class="nf">logs</span><span class="p">():</span>
    <span class="nf">start_capturing_logs</span><span class="p">(</span><span class="s">"dut.log"</span><span class="p">)</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">yield</span>
    <span class="k">finally</span><span class="p">:</span>
        <span class="nf">stop_capturing_logs</span><span class="p">()</span>
</code></pre></div></div>

<p>(I’ll leave the implementation of <em>start_capturing_logs()</em> and
<em>stop_capturing_logs()</em> up to you. 😁)</p>

<p>To use this fixture in any test, just pass it as an argument to the test:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">test_menu_navigation</span><span class="p">(</span><span class="n">logs</span><span class="p">):</span>
    <span class="n">page</span> <span class="o">=</span> <span class="nf">launch_my_app</span><span class="p">()</span>
    <span class="n">page</span> <span class="o">=</span> <span class="n">page</span><span class="p">.</span><span class="nf">navigate_to</span><span class="p">(</span><span class="s">"Settings"</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">page</span><span class="p">.</span><span class="n">selection</span> <span class="o">==</span> <span class="s">"Settings"</span>
</code></pre></div></div>

<p>In the fixture, code before the <em>yield</em> will run before the test; code in the
<em>finally</em> block after the <em>yield</em> will run after the test (even if the test
raised an exception).</p>

<p>To learn more, see <a href="https://docs.pytest.org/en/6.2.x/explanation/fixtures.html">About fixtures</a> in the pytest documentation.</p>

<h3 id="configuration">Configuration</h3>

<p>In Stb-tester v33, using pytest as the test-runner is optional. From Stb-tester
v34, pytest is the default (and only) test-runner.</p>

<p>To enable pytest on v33, use the following configuration in your test-pack’s
<code class="language-plaintext highlighter-rouge">.stbt.conf</code> file (you don’t need this on v34 or newer):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[test_pack]
stbt_version = 33
pytest = true
</code></pre></div></div>

<div class="bs-callout bs-callout-default">

  <p>Changes to the <em>stbt</em> core Python API, and the test-run environment, are
version-controlled. You can specify the version you want to use in your
<code class="language-plaintext highlighter-rouge">.stbt.conf</code> file. This mechanism allows you to upgrade in a controlled manner,
and to test the upgrade on a branch first.</p>

  <p>For more details see the <a href="https://stb-tester.com/manual/release-notes">release notes</a>.</p>

</div>

<h3 id="notes-for-pytest-experts">Notes for pytest experts</h3>

<p>We run each test-case in a separate pytest process. This means that there’s no
difference between “session” fixtures and “function” fixtures — each test
function is run in a separate session.</p>

<p>Support for <a href="https://docs.pytest.org/en/6.2.x/how-to/parametrize.html">parametrized tests</a> is limited: All parametrized variants are run
as a single test-run, so you get a single test-result (a single video, single
logfile, and single pass/fail outcome) for all of them.</p>

<p>Both of these limitations may be removed in a future Stb-tester release.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Lint checks in pull requests</title>
        <pubDate>Mon, 14 Feb 2022 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2022/02/14/lint-checks</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2022/02/14/lint-checks</guid>
        <description>
          &lt;p&gt;When you push a git commit, Stb-tester uses any idle Node in your test-farm to
run a “lint” check using &lt;a href=&quot;https://pylint.org/&quot;&gt;Pylint&lt;/a&gt; to catch common mistakes in your code before
you even run a test.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>When you push a git commit, Stb-tester uses any idle Node in your test-farm to
run a “lint” check using <a href="https://pylint.org/">Pylint</a> to catch common mistakes in your code before
you even run a test.</p>

<p>Previously, we would run Pylint in the cloud. The advantage of running lint on
the Stb-tester Nodes is that we <em>lint</em> your code using exactly the same
environment that we use to <em>run</em> your code when you run a test. This
environment includes any third-party libraries that you install in your <a href="https://stb-tester.com/kb/custom-environment">setup
script</a>, so Pylint will be able to check your code when you call those
libraries. For our customers who install in-house libraries from a server on a
private network, this enables linting that isn’t possible on a cloud-based
runner.</p>

<p>We run Pylint in the background on any idle Node. If you start running a test
on a Node that was running Pylint, Pylint will be transparently re-scheduled
onto a different Node so that it doesn’t interfere with your tests. All of this
is completely automatic and requires no configuration.</p>

<p>Starting today, we are also reporting the lint results using GitHub’s “checks”
API, so the lint warnings appear directly in your pull request:</p>

<figure class="m-b-2">
  <img src="https://stb-tester.com/blog/images/lint-checks.png" class="border" alt="GitHub pull request 'diff' view with check failure: stbt-unused-return-value" />
  <figcaption>
    Lint failure annotation in GitHub pull request
  </figcaption>
</figure>

<p>To learn more about Pylint —including how to configure or disable specific
Pylint checks— see <a href="https://stb-tester.com/blog/2020/01/23/pylint-plugin">Static analysis for your test scripts: Catching common
mistakes before you run the tests!</a></p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New user role: Manual control only
</title>
        <pubDate>Mon, 13 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2021/12/13/manual-control-user-role</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2021/12/13/manual-control-user-role</guid>
        <description>
          The &quot;manual control&quot; role allows users to control devices-under-test 
remotely, and see live video from the devices, without access to the test 
scripts in the test-pack git repository. This allows you to grant access to 
a third party without letting them see the test scripts you have developed 
which are your own intellectual property.

        </description>
        <content:encoded><![CDATA[
          <figure class="pull-right" style="width: 40%">
  <img src="https://stb-tester.com/blog/images/manual-control/stb-tester-portal-screenshot.jpg" />
  <figcaption>
    Manual control and live video<br />
    in the Stb-tester Portal
  </figcaption>
</figure>

<p>The “manual control” role allows users to control devices-under-test remotely,
and see live video from the devices, without access to the test scripts in the
test-pack git repository. This allows you to grant access to a third party
without letting them see the test scripts you have developed which are your own
intellectual property.</p>

<p>The Stb-tester Portal uses GitHub OAuth for single-sign-on: Any users with
read, write, or admin access to the test-pack git repository have access to the
Stb-tester Portal. Users with the new “manual control” role still need a GitHub
account for authentication, but they don’t need access to the git repository.</p>

<table class="table table-striped">
  <thead>
    <tr>
      <th> </th>
      <th>Manual control</th>
      <th>Read</th>
      <th>Write</th>
      <th>Admin</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>See live video from device-under-test</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Save screenshots</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Use remote control</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>See list of git branches &amp; select branch for remote control configuration</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>See all available remote controls</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>See test scripts</td>
      <td>✗</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Modify test scripts</td>
      <td>✗</td>
      <td>✗</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Run tests</td>
      <td>✗</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>See previous test results</td>
      <td>✗</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Access Object Repository</td>
      <td>✗</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Test-farm administration page: See list of Stb-tester Nodes, firmware versions, licenses</td>
      <td>✗</td>
      <td>✓</td>
      <td>✓</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Test-farm administration page: See list of Users</td>
      <td>✗</td>
      <td>✗</td>
      <td>✗</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>Add/remove/change user permissions</td>
      <td>✗</td>
      <td>✗</td>
      <td>✗</td>
      <td>✓</td>
    </tr>
  </tbody>
</table>

<p>To enable this feature, contact <a href="mailto:support@stb-tester.com">support@stb-tester.com</a>.</p>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Testing the Nvidia Shield with Stb-tester
</title>
        <pubDate>Mon, 18 Oct 2021 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2021/10/18/nvidia-shield</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2021/10/18/nvidia-shield</guid>
        <description>
          &lt;p&gt;Stb-tester supports automated testing on the Nvidia Shield TV and Shield TV Pro.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester supports automated testing on the Nvidia Shield TV and Shield TV Pro.</p>

<figure>
  <img src="https://stb-tester.com/blog/images/nvidia-shield/stb-tester-portal-screenshot.jpg" class="border" />
  <figcaption>
    Stb-tester portal showing live video from an Nvidia Shield TV Pro
  </figcaption>
</figure>

<figure class="pull-right">
  <img src="https://stb-tester.com/blog/images/nvidia-shield/control-feedback-cec.png" />
</figure>

<p>To test your app you need a mechanism to <strong>control</strong> the Nvidia Shield and a
way to get <strong>feedback</strong> to check that your app is behaving as it should.</p>

<p>Stb-tester sends CEC commands (over the HDMI connection) to control the Nvidia
Shield, and captures the video over the same HDMI connection to verify your
app’s behaviour. <a href="https://en.wikipedia.org/wiki/Consumer_Electronics_Control">CEC</a> is the same technology that allows you to use your TV’s
remote to control the Nvidia Shield.</p>

<h3 id="reliability">Reliability</h3>

<p>The Shield’s CEC implementation is very reliable: We ran a soak test for 32
hours and there were 0 missed keypress or “double” keypress in 73,000
keypresses. The test script used <a href="https://stb-tester.com/manual/python-api#stbt.press">stbt.press</a> to navigate right &amp; left on the
Shield’s home screen, and after every keypress it checked that the selection
had moved to the expected position.</p>

<h3 id="setup">Setup</h3>

<p>You will need our USB CEC adapter. If we didn’t ship one with your Stb-tester
device, contact <a href="mailto:support@stb-tester.com">support@stb-tester.com</a>. Plug it in as per the diagram below
— the Stb-tester Node will automatically use the CEC adapter if there is no
infrared transmitter connected.</p>

<p>CEC is enabled by default on the Nvidia Shield.</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/nvidia-shield/wiring.png" class="text-center" /></p>
</figure>

<h3 id="key-names">Key names</h3>

<p>In your test scripts you can use the following key names:</p>

<figure class="pull-right">
  <img src="https://stb-tester.com/blog/images/nvidia-shield/remote-control.jpg" />
</figure>

<ul>
  <li><code class="language-plaintext highlighter-rouge">"KEY_POWER"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_UP"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_DOWN"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_LEFT"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_RIGHT"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_OK"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_BACK"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_HOME"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_FASTFORWARD"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_PLAY"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_PAUSE"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_REWIND"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_VOLUMEUP"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_VOLUMEDOWN"</code></li>
</ul>

<p><code class="language-plaintext highlighter-rouge">"KEY_PLAY"</code> and <code class="language-plaintext highlighter-rouge">"KEY_PAUSE"</code> work as expected (tested in the YouTube app).
There’s no equivalent of the single “play/pause” button on the remote control
(that is, CEC’s <code class="language-plaintext highlighter-rouge">"KEY_PLAY"</code> doesn’t pause and <code class="language-plaintext highlighter-rouge">"KEY_PAUSE"</code> doesn’t play) so
in the remote control image for the Stb-tester Portal’s “manual control”
clicking the left half of the button sends <code class="language-plaintext highlighter-rouge">"KEY_PLAY"</code> and the right half
sends <code class="language-plaintext highlighter-rouge">"KEY_PAUSE"</code>.</p>

<p><code class="language-plaintext highlighter-rouge">"KEY_FASTFORWARD"</code> and <code class="language-plaintext highlighter-rouge">"KEY_REWIND"</code> work as expected (tested in the YouTube
app). With long-hold the fast-forwarding/rewinding isn’t as fast as with the
real remote control, but it does work.</p>

<p>There’s no CEC equivalent to the Settings button (we tried all 256 CEC key
codes). However, you can still get to the same Settings menu by navigating to
the gear icon on the home screen using <code class="language-plaintext highlighter-rouge">"KEY_UP"</code>, <code class="language-plaintext highlighter-rouge">"KEY_RIGHT"</code> and
<code class="language-plaintext highlighter-rouge">"KEY_OK"</code>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>SSL errors accessing Stb-tester REST API from RHEL/CentOS 7</title>
        <pubDate>Fri, 01 Oct 2021 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2021/10/01/ssl-errors-with-rhel-centos-7</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2021/10/01/ssl-errors-with-rhel-centos-7</guid>
        <description>
          &lt;p&gt;If you started getting errors today when accessing the REST API on your
Stb-tester Portal (either directly using curl or python &lt;a href=&quot;https://docs.python-requests.org/&quot;&gt;requests&lt;/a&gt;, or using
our &lt;a href=&quot;https://stb-tester.com/manual/stbt-rig&quot;&gt;stbt_rig.py&lt;/a&gt; command-line tool) then read on for the solution.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>If you started getting errors today when accessing the REST API on your
Stb-tester Portal (either directly using curl or python <a href="https://docs.python-requests.org/">requests</a>, or using
our <a href="https://stb-tester.com/manual/stbt-rig">stbt_rig.py</a> command-line tool) then read on for the solution.</p>

<p>The Stb-tester Portal uses TLS certificates issued by <a href="https://letsencrypt.org/">Let’s Encrypt</a>. Due to
<a href="https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/">certain technical decisions</a> made by Let’s Encrypt, software
using older versions of OpenSSL (before 1.1.0) can get “certificate expired”
errors when trying to access the Stb-tester Portal after 30 September 2021.
Specifically RHEL/CentOS 7 are affected, as they use OpenSSL 1.0.2k.</p>

<p>The fix requires 2 steps:</p>

<ol>
  <li>
    <p>If you are using RHEL/CentOS 7, upgrade your <strong>ca-certificates</strong> package.
This issue was fixed in ca-certificates version 2021.2.50–72.</p>
  </li>
  <li>
    <p>If you are using the Python <em>requests</em> library (or <em>stbt_rig.py</em>) then you
need to tell <em>requests</em> to use the operating system’s CA (Certificate
Authority) bundle, mentioned above, instead of the CA bundle that’s embedded
into the <em>requests</em> library itself. To do this, set the environment variable
<em>REQUESTS_CA_BUNDLE</em> before running <em>stbt_rig.py</em>, like this:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt
</code></pre></div>    </div>
  </li>
</ol>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New features: Prometheus/Grafana integration; Bluetooth &amp; RF4CE support; interactive log viewer
</title>
        <pubDate>Tue, 07 Sep 2021 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2021/09/07/bluetooth-grafana-and-log-viewer</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2021/09/07/bluetooth-grafana-and-log-viewer</guid>
        <description>
          &lt;p&gt;2021 has been a busy year for us, with major new features implemented to
support specific customers’ requirements: &lt;strong&gt;Prometheus and Grafana dashboards&lt;/strong&gt;
for logging and reporting performance measurements and other real-time metrics;
support for some &lt;strong&gt;Bluetooth and RF4CE remote controls&lt;/strong&gt; via RedRat-X hardware;
an &lt;strong&gt;interactive log viewer with frame-accurate synchronisation&lt;/strong&gt; between each
log line and the video recording of the test-run; and a new &lt;strong&gt;test-farm
administration&lt;/strong&gt; page.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>2021 has been a busy year for us, with major new features implemented to
support specific customers’ requirements: <strong>Prometheus and Grafana dashboards</strong>
for logging and reporting performance measurements and other real-time metrics;
support for some <strong>Bluetooth and RF4CE remote controls</strong> via RedRat-X hardware;
an <strong>interactive log viewer with frame-accurate synchronisation</strong> between each
log line and the video recording of the test-run; and a new <strong>test-farm
administration</strong> page.</p>

<p>We have other major new features in the works, but the ones in this article are
available now to all our customers.</p>

<h3 id="prometheus--grafana-integration">Prometheus &amp; Grafana integration</h3>

<p><a href="https://prometheus.io/">Prometheus</a> is an open-source time-series database used for monitoring and
alerting. <a href="https://grafana.com/">Grafana</a> is an open-source dashboard and visualisation tool that
integrates with Prometheus.</p>

<figure>
<img src="https://stb-tester.com/blog/images/prometheus/grafana.png" />
<figcaption style="margin-top:-30px">
Grafana dashboard showing performance measurements.<br />
Note the median (yellow), 95th percentile (green), and alarm threshold (red).
</figcaption>
</figure>

<p>Using our new <a href="https://stb-tester.com/manual/python-api#stbt.prometheus.Counter">stbt.prometheus</a> APIs, your test-scripts can record measurements
for ingestion into a Prometheus database. On your Stb-tester Portal we can host
a Prometheus &amp; Grafana instance for you; or you can ingest the data into your
own Prometheus instance.</p>

<p>Example use-cases include:</p>

<ul>
  <li>Counters: The number of times the “buffering” indicator or “loading” spinner
has appeared; the number of black frames or glitches seen; the number of
tests that failed, or the number of VoD assets that failed to play; etc.</li>
  <li>Gauges: Measuring the temperature or memory usage on your device-under-test.</li>
  <li>Histograms: Performance measurements such as channel-change duration, app
launch duration, or time for VoD content to start playing.</li>
</ul>

<p>Since your tests are written in Python, performance measurements are as simple
as calling <em><a href="https://docs.python.org/3/library/time.html#time.time">time.time()</a></em> before and after the action you want to measure.</p>

<h3 id="bluetooth--rf4ce-remote-controls">Bluetooth &amp; RF4CE remote controls</h3>

<p>The Stb-tester <em><a href="https://stb-tester.com/manual/python-api#stbt.press">press()</a></em> API now supports <a href="https://www.redrat.co.uk/products/redrat-x/">RedRat-X</a> hardware, to control the
device-under-test via Bluetooth or RF4CE (ZigBee).</p>

<p>RedRat-X hardware must be purchased separately. Contact <a href="https://www.redrat.co.uk/">RedRat</a> to see if
your particular remote control is supported. Typically there is a one-off
onboarding fee to support new remote controls.</p>

<h3 id="interactive-log-viewer">Interactive log viewer</h3>

<p>In the Stb-tester Portal’s test-results view, click on the timestamp of any
log line to seek the video to that exact time:</p>

<figure>
  <video controls="" class="border screenshot">
    <source src="https://stb-tester.com/blog/images/prometheus/log-viewer.mov" />
  </video>
</figure>

<p>This log is anything printed by your test-script — for example using Python’s
<em>print()</em> function, or the debug information that is automatically printed when
you call Stb-tester APIs.</p>

<p>The timestamp at the left of each line is the time when Python printed the
line. Timestamps from a specific frame of video are also clickable — for
example <em><a href="https://stb-tester.com/manual/python-api#stbt.Frame">Frame.time</a></em>, <em><a href="https://stb-tester.com/manual/python-api#stbt.MatchResult">MatchResult.time</a></em> and <em><a href="https://stb-tester.com/manual/python-api#stbt.MotionResult">MotionResult.time</a></em> in the
debug output from various Stb-tester APIs. These frame timestamps come from our
HDMI video-capture hardware. If you’re confused by the difference, consider
this koan:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">frame</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">get_frame</span><span class="p">()</span>
<span class="n">time</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"The frame was captured at: </span><span class="si">{</span><span class="n">frame</span><span class="p">.</span><span class="n">time</span><span class="si">}</span><span class="s">"</span><span class="p">)</span></code></pre>
</figure>

<p>The output would be something like this:</p>

<figure class="border">
<img src="https://stb-tester.com/blog/images/prometheus/log-line.png" alt="15:19:35.374 The frame was captured at: 1630678765.206 [15:19:25.206]" />
</figure>

<p>The log viewer shows timestamps in your local timezone. (In the raw log file,
the timestamps are always in UTC.)</p>

<p>We have also added controls to the video player to step forward or backward by
1 frame at a time, or 1 second, or 10 seconds.</p>

<p>Our new log viewer is a very powerful tool for triaging test failures and
understanding the behaviour of your device-under-test. It’s also useful for
spot-checking performance measurements, to gain confidence in the correctness
of your test scripts!</p>

<h3 id="test-farm-administration-interface">Test-farm administration interface</h3>

<p>The new <strong>Test-farm administration</strong> page in the Stb-tester Portal lists all of
your Stb-tester Nodes, with their current firmware version and license
expiration dates. It allows you to reboot Stb-tester Nodes, force HDMI
re-negotiation to simulate hot-plugging the HDMI cable, and install firmware
updates.</p>

<p>Access this page from the user drop-down menu in the top right corner of the
Stb-tester Portal:</p>

<figure class="screenshot">
<img src="https://stb-tester.com/blog/images/prometheus/test-farm-admin-link.png" />
</figure>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New instructions for VSCode setup on Windows</title>
        <pubDate>Tue, 24 Aug 2021 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2021/08/24/updated-instructions-for-vscode-setup</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2021/08/24/updated-instructions-for-vscode-setup</guid>
        <description>
          &lt;p&gt;Our VSCode IDE integration makes test-script development convenient. With it you get:&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Our VSCode IDE integration makes test-script development convenient. With it you get:</p>

<ol>
  <li>Auto-complete for stbt APIs.</li>
  <li>API documentation in the IDE.</li>
  <li>Linting results in the IDE including stbt specific lints.</li>
  <li>Run tests directly from within the IDE, without having to commit or push.</li>
  <li>Automatically pushing test-code to the portal for instant feedback in the
Object Repository during test script development.</li>
</ol>

<p>See it in action here:</p>

<video controls="" preload="metadata" src="https://stb-tester.com/blog/images/new-vscode-setup/vscode-demo.mp4" poster="/blog/images/new-vscode-setup/vscode-demo.jpg"></video>

<p>We have recently revamped our tooling and instructions to make it easy to get
started on Windows. There’s now a step-by-step guide (with videos). If you had
problems setting up VSCode before, now is a great time to try again.</p>

<p>Check it out here in our manual: <a href="https://stb-tester.com/manual/ide/vscode">Development
Environment Setup with VSCode</a>. If you have any difficulty please contact us
on <a href="mailto:support@stb-tester.com">support@stb-tester.com</a> and we will help to resolve any issues.</p>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Better latency in Stb-tester&apos;s live video (goodbye Flash, hello WebRTC)</title>
        <pubDate>Thu, 24 Dec 2020 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2020/12/24/webrtc-live-video</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2020/12/24/webrtc-live-video</guid>
        <description>
          &lt;p&gt;The Stb-tester Portal shows live video from the device-under-test, right in
your web browser. Previously, this video was streamed from your Stb-tester
Nodes using RTMP (Flash’s video-streaming protocol). With our latest firmware
release, Stb-tester Nodes now stream this live audio &amp;amp; video using &lt;strong&gt;WebRTC&lt;/strong&gt;
– the W3C standard protocol for low-latency, real-time communication. WebRTC
allowed us to achieve &lt;strong&gt;much better latency&lt;/strong&gt; when you are interacting manually
with your device-under-test via the Stb-tester Portal.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>The Stb-tester Portal shows live video from the device-under-test, right in
your web browser. Previously, this video was streamed from your Stb-tester
Nodes using RTMP (Flash’s video-streaming protocol). With our latest firmware
release, Stb-tester Nodes now stream this live audio &amp; video using <strong>WebRTC</strong>
– the W3C standard protocol for low-latency, real-time communication. WebRTC
allowed us to achieve <strong>much better latency</strong> when you are interacting manually
with your device-under-test via the Stb-tester Portal.</p>

<div class="bs-callout bs-callout-default">
  <p>Note: Everything in this article is about the live video streamed to web
browsers for <strong>humans</strong> to see. It isn’t related to Stb-tester’s video-capture
when running automated tests, which isn’t affected by this change.</p>
</div>

<h3 id="low-latency">Low latency</h3>

<p>The latency improvements are best shown with a video. Here we have 2 browser
windows open at the same page. The left window is using WebRTC, and the right
window is using Flash:</p>

<figure>
  <video controls="" class="border screenshot">
    <source src="https://stb-tester.com/blog/images/webrtc/WebRTC vs Flash.mov" />
  </video>
</figure>

<p>With WebRTC the latency is under 500ms, and this includes the time to send the
“keypress” instruction to the Stb-tester Node. Flash’s latency is around 2
seconds.</p>

<h3 id="nat-traversal">NAT traversal</h3>

<p>WebRTC uses peer-to-peer communication to stream the video from the Stb-tester
Node to your web browser — the video traffic doesn’t go via the Stb-tester
Portal. If your PC is on the same network as the Stb-tester Node, the traffic
doesn’t leave your local network.</p>

<p>This was also true of our old Flash video implementation; the difference is
that WebRTC also has the ability to negotiate a peer-to-peer connection when
you are on different networks, using <a href="https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment">ICE</a> protocols. However, this only works
with <em>some</em> firewalls/NATs.</p>

<p>When direct peer-to-peer communication isn’t possible, WebRTC allows tunnelling
the video traffic through a <a href="https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT">TURN</a> server. Contact <a href="mailto:sales@stb-tester.com">sales@stb-tester.com</a> if
you want this capability.</p>

<p>Note that the Stb-tester Portal has always had a fallback mechanism for
viewers on a different network, using JPEG thumbnails at 1 frame per second —
see <a href="https://stb-tester.com/manual/user-interface-reference#live-video-formats">Live video formats</a> in the Stb-tester manual for details.</p>

<p>WebRTC traffic is secured using industry-standard <a href="https://en.wikipedia.org/wiki/Datagram_Transport_Layer_Security">DTLS</a> encryption.</p>

<h3 id="flash-reaches-end-of-life-soon">Flash reaches end-of-life soon</h3>

<p>Flash is being <a href="https://www.adobe.com/products/flashplayer/end-of-life.html">discontinued by Adobe</a> after December 31 2020, and major
browsers will disable their Flash support entirely.</p>

<p>To summarise:</p>

<table class="table table-striped table-condensed">
  <thead>
    <tr>
      <th>WebRTC</th>
      <th>Flash</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>&lt;500ms latency</td>
      <td>2+ seconds latency</td>
    </tr>
    <tr>
      <td>Peer to peer with NAT traversal</td>
      <td>Peer to peer (local network only)</td>
    </tr>
    <tr>
      <td>DTLS encryption</td>
      <td>Unencrypted video (local network only)</td>
    </tr>
    <tr>
      <td>W3C standard</td>
      <td>Proprietary protocol</td>
    </tr>
    <tr>
      <td>Supported by all major browsers</td>
      <td>Requires Flash browser plugin</td>
    </tr>
    <tr>
      <td>Future proof</td>
      <td>Discontinued Jan 2021</td>
    </tr>
  </tbody>
</table>

<h3 id="selecting-the-video-format-in-the-stb-tester-portal">Selecting the video format in the Stb-tester portal</h3>

<p>To use WebRTC, click the settings icon underneath the video:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/webrtc/live-video.png" class="border" alt="'Manual control' view in the Stb-tester Portal, with live video" />
  <figcaption>
    "Video format" menu in the Stb-tester Portal
  </figcaption>
</figure>

<h3 id="browser-settings">Browser settings</h3>

<p>You may need to enable “auto-play” for the Stb-tester Portal. For example on
Firefox, click the “permissions” icon next to the URL bar:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/webrtc/firefox-permissions.png" class="border" />
  <figcaption>
    Firefox site permissions
  </figcaption>
</figure>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Testing on-screen keyboards with Stb-tester</title>
        <pubDate>Fri, 02 Oct 2020 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2020/10/02/testing-onscreen-keyboards</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2020/10/02/testing-onscreen-keyboards</guid>
        <description>
          &lt;p&gt;Stb-tester v32 added new APIs that make it much easier to navigate on-screen
keyboards from your test scripts. In this tutorial we’ll model the behaviour of
YouTube’s search keyboard on the Apple TV, and we’ll write a Page Object (a
Python class) to navigate the keyboard from our test scripts.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester v32 added new APIs that make it much easier to navigate on-screen
keyboards from your test scripts. In this tutorial we’ll model the behaviour of
YouTube’s search keyboard on the Apple TV, and we’ll write a Page Object (a
Python class) to navigate the keyboard from our test scripts.</p>

<p>By the end of this tutorial we will have implemented a Page Object with an
<code class="language-plaintext highlighter-rouge">enter_text</code> method, so you can write a test script like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">page</span> <span class="o">=</span> <span class="nc">Search</span><span class="p">()</span>
<span class="n">page</span><span class="p">.</span><span class="nf">enter_text</span><span class="p">(</span><span class="s">"Peppa Pig"</span><span class="p">)</span></code></pre>
</figure>

<p>Watch it in action:</p>

<video class="screenshot" controls="true">
  <source src="https://stb-tester.com/blog/images/keyboard/video.mp4" />
</video>

<h3 id="modelling-the-keyboard">Modelling the keyboard</h3>

<p>First we will specify a <a href="https://en.wikipedia.org/wiki/Directed_graph">Directed Graph</a> that describes the behaviour of the
keyboard under test. A “graph” (in the computer-science sense of the word)
consists of “nodes” connected by “edges”. Each <strong>key</strong> on our keyboard will be
a node in our graph, and the possible <strong>transitions</strong> from each key to its
neighbours will be the edges:</p>

<figure class="text-center overlap-caption">
  <img src="https://stb-tester.com/blog/images/keyboard/Figure1.png" />
  <figcaption>
    Figure 1: YouTube's search keyboard on Apple TV, showing the outgoing
    transitions from key "a".
  </figcaption>
</figure>

<p>Each edge specifies the button that you need to press on the remote control to
trigger that transition: <code class="language-plaintext highlighter-rouge">KEY_RIGHT</code>, <code class="language-plaintext highlighter-rouge">KEY_LEFT</code>, <code class="language-plaintext highlighter-rouge">KEY_UP</code>, or <code class="language-plaintext highlighter-rouge">KEY_DOWN</code>.</p>

<p>Stb-tester will use this graph to calculate the <strong>shortest path</strong> to the
target. For example, if the current selection is on “a”, to type the letter “p”
Stb-tester would press <code class="language-plaintext highlighter-rouge">KEY_RIGHT</code> 3 times and <code class="language-plaintext highlighter-rouge">KEY_DOWN</code> twice (and then
<code class="language-plaintext highlighter-rouge">KEY_OK</code> to type the selected letter). Note that there can be more than one
shortest path.</p>

<figure class="text-center overlap-caption">
  <img src="https://stb-tester.com/blog/images/keyboard/Figure2.png" />
  <figcaption>
    Figure 2: Shortest path from "a" to "p".
  </figcaption>
</figure>

<p>To model this keyboard’s behaviour in our Python code, we can use the
<a href="https://stb-tester.com/manual/python-api#stbt.Keyboard">stbt.Keyboard</a> API to specify each key’s <strong>name</strong> and <strong>region</strong> (its position
on the screen; we’ll use this later) using <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_key">Keyboard.add_key</a>, and the
transitions between keys using <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_transition">Keyboard.add_transition</a>, like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">kb</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Keyboard</span><span class="p">()</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_key</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">"a"</span><span class="p">,</span> <span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">175</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">50</span><span class="p">))</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_key</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">"b"</span><span class="p">,</span> <span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">175</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">175</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">50</span><span class="p">))</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">)</span>
<span class="c1"># ...and so on for all the other keys...</span></code></pre>
</figure>

<p>Hold on, hold on. This keyboard is laid out in a regular grid, so instead of
typing each key one by one, let’s use <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_grid">Keyboard.add_grid</a>. Our keyboard has 3
different grids, shown below in different colours:</p>

<figure class="text-center overlap-caption">
  <img src="https://stb-tester.com/blog/images/keyboard/Figure3.png" />
  <figcaption>
    Figure 3: The keys are laid out in a 3x1 grid (yellow), a 6x6 grid (red),
    and another 3x1 grid (blue).
  </figcaption>
</figure>

<p>We specify all these keys like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">kb</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Keyboard</span><span class="p">()</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_grid</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">145</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">410</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">160</span><span class="p">),</span>
                      <span class="n">data</span><span class="o">=</span><span class="p">[[</span><span class="s">"abc"</span><span class="p">,</span> <span class="s">"ABC"</span><span class="p">,</span> <span class="s">"#+-"</span><span class="p">]]))</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_grid</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">175</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">425</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">475</span><span class="p">),</span>
                      <span class="n">data</span><span class="o">=</span><span class="p">[</span><span class="s">"abcdef"</span><span class="p">,</span>
                            <span class="s">"ghijkl"</span><span class="p">,</span>
                            <span class="s">"mnopqr"</span><span class="p">,</span>
                            <span class="s">"stuvwx"</span><span class="p">,</span>
                            <span class="s">"yz1234"</span><span class="p">,</span>
                            <span class="s">"567890"</span><span class="p">]))</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_grid</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">480</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">425</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">520</span><span class="p">),</span>
                      <span class="n">data</span><span class="o">=</span><span class="p">[[</span><span class="s">" "</span><span class="p">,</span> <span class="s">"DELETE"</span><span class="p">,</span> <span class="s">"CLEAR"</span><span class="p">]]))</span></code></pre>
</figure>

<p>Much easier, isn’t it! <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_grid">Keyboard.add_grid</a> is only suitable if all the cells in
the grid are the same size. You don’t need to be super-precise with the region
coordinates — just make sure the centre of each key is inside the right cell.</p>

<div class="bs-callout bs-callout-default">
  <p>Note that <a href="https://stb-tester.com/manual/python-api#stbt.Grid">stbt.Grid</a>’s <em>data</em> parameter is a list of lists. Actually it’s a
list of <em>iterables</em> — that’s why we can provide a list of strings because
iterating over a string yields one character at a time. We could also have
specified it like this:</p>

  <figure class="highlight">
    <pre><code class="language-python" data-lang="python">        <span class="n">data</span><span class="o">=</span><span class="p">[</span>
            <span class="p">[</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"c"</span><span class="p">,</span> <span class="s">"d"</span><span class="p">,</span> <span class="s">"e"</span><span class="p">,</span> <span class="s">"f"</span><span class="p">],</span>
            <span class="p">[</span><span class="s">"g"</span><span class="p">,</span> <span class="s">"h"</span><span class="p">,</span> <span class="s">"i"</span><span class="p">,</span> <span class="s">"j"</span><span class="p">,</span> <span class="s">"k"</span><span class="p">,</span> <span class="s">"l"</span><span class="p">],</span>
            <span class="p">...</span><span class="n">etc</span><span class="p">...</span>
        <span class="p">]</span></code></pre>
  </figure>

  <p>…but the first way is easier to type and easier to read. For the top and
bottom grids we do have to use a list of lists because the key names are
longer than a single character.</p>
</div>

<p><a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_grid">Keyboard.add_grid</a> will add all the keys and the transitions between them
(within the grid). It won’t add transitions that go <em>outside</em> of the grid, so
we need to add those explicitly, like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="c1"># abc ABC #+-
# ↕ ↕ ↕ ↕ ↕ ↕
# a b c d e f
</span><span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"abc"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"b"</span><span class="p">,</span> <span class="s">"abc"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"c"</span><span class="p">,</span> <span class="s">"ABC"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"d"</span><span class="p">,</span> <span class="s">"ABC"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"e"</span><span class="p">,</span> <span class="s">"#+-"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"f"</span><span class="p">,</span> <span class="s">"#+-"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">)</span>

<span class="c1"># 5 6 7 8 9 0
# ↕ ↕ ↕ ↕ ↕ ↕
# SPC DEL CLR
</span><span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"5"</span><span class="p">,</span> <span class="s">" "</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"6"</span><span class="p">,</span> <span class="s">" "</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"7"</span><span class="p">,</span> <span class="s">"DELETE"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"8"</span><span class="p">,</span> <span class="s">"DELETE"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"9"</span><span class="p">,</span> <span class="s">"CLEAR"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">)</span>
<span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="s">"0"</span><span class="p">,</span> <span class="s">"CLEAR"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">)</span></code></pre>
</figure>

<p>Note that, by default, <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_transition">Keyboard.add_transition</a> adds the opposite transition
automatically, for example <code class="language-plaintext highlighter-rouge">KEY_UP</code> for <code class="language-plaintext highlighter-rouge">KEY_DOWN</code> or <code class="language-plaintext highlighter-rouge">KEY_LEFT</code> for
<code class="language-plaintext highlighter-rouge">KEY_RIGHT</code>.</p>

<p>You may have noticed that some keys have <em>two</em> possible transitions for the
same remote-control button — for example pressing <code class="language-plaintext highlighter-rouge">KEY_UP</code> from “SPACE” can
land on “5” or on “6”. This reflects the keyboard-under-test’s real behaviour:
It remembers which key you came from before navigating down onto “SPACE”, and
it returns to the same column when you go back up. <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard">stbt.Keyboard</a> doesn’t keep
track of this state, so we just accept that both of those two keys (“5” and
“6”) are valid targets.</p>

<h3 id="modes">Modes</h3>

<p>This keyboard has three different <strong>modes</strong>: Lowercase, uppercase, and symbols.
It’s best to think of each mode as an entirely different keyboard, with
transitions that change between them: Pressing <code class="language-plaintext highlighter-rouge">KEY_OK</code> on one of the mode keys
(like “ABC”) will go to that mode.</p>

<figure class="text-center overlap-caption">
  <img src="https://stb-tester.com/blog/images/keyboard/Figure4.png" />
  <figcaption>
    Figure 4: Lowercase, uppercase, and symbols modes.
  </figcaption>
</figure>

<p>Some keys might appear in more than one mode. It’s important to model these as
different keys — even though they look the same, they are different because
they are connected to different keys. For example there is a “SPACE” key in all
of the modes, but pressing <code class="language-plaintext highlighter-rouge">KEY_UP</code> from it will go to a totally different key:</p>

<figure class="text-center overlap-caption">
  <img src="https://stb-tester.com/blog/images/keyboard/Figure5.png" />
  <figcaption>
    Figure 5: The "SPACE" keys in the 3 modes are modelled as 3 different keys.
  </figcaption>
</figure>

<p>The same is true for the “DELETE” and “CLEAR” keys, the mode keys (“abc”,
“ABC”, and “#+-“), and the number keys.</p>

<p>To tell these apart in our model we specify <strong>mode</strong> when we call
<a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_key">Keyboard.add_key</a>, <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_transition">Keyboard.add_transition</a>, or <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_grid">Keyboard.add_grid</a>, like
this:</p>

<figure class="highlight modes">
  <pre><code class="language-python" data-lang="python"><span class="n">top_grid</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">145</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">410</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">160</span><span class="p">),</span>
                     <span class="n">data</span><span class="o">=</span><span class="p">[[</span><span class="s">"abc"</span><span class="p">,</span> <span class="s">"ABC"</span><span class="p">,</span> <span class="s">"#+-"</span><span class="p">]])</span>
<span class="n">bottom_grid</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">480</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">425</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">520</span><span class="p">),</span>
                        <span class="n">data</span><span class="o">=</span><span class="p">[[</span><span class="s">" "</span><span class="p">,</span> <span class="s">"DELETE"</span><span class="p">,</span> <span class="s">"CLEAR"</span><span class="p">]])</span>
<span class="n">middle_region</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">175</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">425</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">475</span><span class="p">)</span>
<span class="n">middle_grids</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"lowercase"</span><span class="p">:</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">middle_region</span><span class="p">,</span>
                           <span class="n">data</span><span class="o">=</span><span class="p">[</span><span class="s">"abcdef"</span><span class="p">,</span>
                                 <span class="s">"ghijkl"</span><span class="p">,</span>
                                 <span class="s">"mnopqr"</span><span class="p">,</span>
                                 <span class="s">"stuvwx"</span><span class="p">,</span>
                                 <span class="s">"yz1234"</span><span class="p">,</span>
                                 <span class="s">"567890"</span><span class="p">]),</span>
    <span class="s">"uppercase"</span><span class="p">:</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">middle_region</span><span class="p">,</span>
                           <span class="n">data</span><span class="o">=</span><span class="p">[</span><span class="s">"ABCDEF"</span><span class="p">,</span>
                                 <span class="s">"GHIJKL"</span><span class="p">,</span>
                                 <span class="s">"MNOPQR"</span><span class="p">,</span>
                                 <span class="s">"STUVWX"</span><span class="p">,</span>
                                 <span class="s">"YZ1234"</span><span class="p">,</span>
                                 <span class="s">"567890"</span><span class="p">]),</span>
    <span class="s">"symbols"</span><span class="p">:</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">middle_region</span><span class="p">,</span>
                         <span class="n">data</span><span class="o">=</span><span class="p">[</span><span class="s">"!@#$%&amp;"</span><span class="p">,</span>
                               <span class="s">"~*</span><span class="se">\\</span><span class="s">/?^"</span><span class="p">,</span>
                               <span class="s">"_`;:|="</span><span class="p">,</span>
                               <span class="s">"éñ[]{}"</span><span class="p">,</span>
                               <span class="s">"çü.,+-"</span><span class="p">,</span>
                               <span class="s">"&lt;&gt;()'</span><span class="se">\"</span><span class="s">"</span><span class="p">]),</span>
<span class="p">}</span>

<span class="n">kb</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nc">Keyboard</span><span class="p">()</span>
<span class="k">for</span> <span class="n">mode</span> <span class="ow">in</span> <span class="p">[</span><span class="s">"lowercase"</span><span class="p">,</span> <span class="s">"uppercase"</span><span class="p">,</span> <span class="s">"symbols"</span><span class="p">]:</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_grid</span><span class="p">(</span><span class="n">top_grid</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_grid</span><span class="p">(</span><span class="n">bottom_grid</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">g</span> <span class="o">=</span> <span class="n">middle_grids</span><span class="p">[</span><span class="n">mode</span><span class="p">]</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_grid</span><span class="p">(</span><span class="n">g</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>

    <span class="c1"># Transitions between grids:
</span>    <span class="c1">#
</span>    <span class="c1"># abc ABC #+-  (top grid)
</span>    <span class="c1"># ↕ ↕ ↕ ↕ ↕ ↕
</span>    <span class="c1"># a b c d e f  (first row of middle grid)
</span>    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"abc"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"abc"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"ABC"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"ABC"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"#+-"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"#+-"</span><span class="p">,</span> <span class="s">"KEY_UP"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>

    <span class="c1"># 5 6 7 8 9 0  (last row of middle grid)
</span>    <span class="c1"># ↕ ↕ ↕ ↕ ↕ ↕
</span>    <span class="c1"># SPC DEL CLR  (bottom grid)
</span>    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">" "</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">" "</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"DELETE"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"DELETE"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"CLEAR"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>
    <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">g</span><span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">].</span><span class="n">data</span><span class="p">,</span> <span class="s">"CLEAR"</span><span class="p">,</span> <span class="s">"KEY_DOWN"</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span></code></pre>
</figure>

<p>Now we just need to add the transitions between modes: If we’re in lowercase
mode with the selection on “ABC”, pressing <code class="language-plaintext highlighter-rouge">KEY_OK</code> takes us to uppercase mode
with the selection still on “ABC” (see Figure 4) — and so on for the other
mode keys:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">for</span> <span class="n">source_mode</span> <span class="ow">in</span> <span class="p">[</span><span class="s">"lowercase"</span><span class="p">,</span> <span class="s">"uppercase"</span><span class="p">,</span> <span class="s">"symbols"</span><span class="p">]:</span>
    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">target_mode</span> <span class="ow">in</span> <span class="p">[(</span><span class="s">"abc"</span><span class="p">,</span> <span class="s">"lowercase"</span><span class="p">),</span>
                              <span class="p">(</span><span class="s">"ABC"</span><span class="p">,</span> <span class="s">"uppercase"</span><span class="p">),</span>
                              <span class="p">(</span><span class="s">"#+-"</span><span class="p">,</span> <span class="s">"symbols"</span><span class="p">)]:</span>
        <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">kb</span><span class="p">.</span><span class="nf">find_key</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">source_mode</span><span class="p">),</span>
                          <span class="n">kb</span><span class="p">.</span><span class="nf">find_key</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">target_mode</span><span class="p">),</span>
                          <span class="s">"KEY_OK"</span><span class="p">)</span></code></pre>
</figure>

<p>Note that in this keyboard we can identify a key unambiguously by its <strong>name +
mode</strong>. Some keyboards might have the same key twice in two different places in
the same mode (for example two “shift” keys) — in that case you would model
this as two separate keys with the same name &amp; mode, but different <strong>region</strong>.</p>

<p>This keyboard has another way of changing modes: Pressing <code class="language-plaintext highlighter-rouge">KEY_PLAY</code> cycles
through the modes. For example from “a” to “A” to “!” and back to “a”; or from
“b” to “B” to “@”, etc.</p>

<figure id="figure6" class="text-center overlap-caption">
  <img src="https://stb-tester.com/blog/images/keyboard/Figure6.png" />
  <figcaption>
    Figure 6: Mode change from any key by pressing <code>KEY_PLAY</code>.
  </figcaption>
</figure>

<p>To model this we need to add each a transition from each and every key in the
keyboard. We can use <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.find_keys">Keyboard.find_keys</a> to loop over the keys we have already
added to the model, and <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.find_key">Keyboard.find_key</a> (singular) to find the
corresponding target for each transition, like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">for</span> <span class="n">source_mode</span><span class="p">,</span> <span class="n">target_mode</span> <span class="ow">in</span> <span class="p">[(</span><span class="s">"lowercase"</span><span class="p">,</span> <span class="s">"uppercase"</span><span class="p">),</span>
                                 <span class="p">(</span><span class="s">"uppercase"</span><span class="p">,</span> <span class="s">"symbols"</span><span class="p">),</span>
                                 <span class="p">(</span><span class="s">"symbols"</span><span class="p">,</span> <span class="s">"lowercase"</span><span class="p">)]:</span>
    <span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">kb</span><span class="p">.</span><span class="nf">find_keys</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="n">source_mode</span><span class="p">):</span>
        <span class="n">target</span> <span class="o">=</span> <span class="n">kb</span><span class="p">.</span><span class="nf">find_key</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">key</span><span class="p">.</span><span class="n">region</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">target_mode</span><span class="p">)</span>
        <span class="n">kb</span><span class="p">.</span><span class="nf">add_transition</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="s">"KEY_PLAY"</span><span class="p">)</span></code></pre>
</figure>

<h3 id="identifying-the-currently-selected-key">Identifying the currently selected key</h3>

<p>We have modelled the keyboard’s behaviour. Now, to <em>use</em> that model we need to
understand the current state of the device under test: Which key is currently
selected?</p>

<p>With Stb-tester, the way we extract this information from the screen is to
write a <strong>Page Object</strong> (a Python class) that does the necessary
image-processing. Our Page Object class will have two properties:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">is_visible</code>: Returns True if the keyboard is visible and focused.</li>
  <li><code class="language-plaintext highlighter-rouge">selection</code>: Returns the currently selected key.</li>
</ul>

<p>We can answer both of these questions (Is the keyboard visible? And which key
is selected?) with <a href="https://stb-tester.com/manual/python-api#stbt.find_selection_from_background">stbt.find_selection_from_background</a>. Let’s start with a
simple example that only understands the lowercase keyboard:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">Search</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="s">"""The YouTube search keyboard on Apple TV."""</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">is_visible</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="nf">bool</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">selection</span><span class="p">)</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">selection</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">match</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">find_selection_from_background</span><span class="p">(</span>
            <span class="s">"lowercase-background.png"</span><span class="p">,</span>
            <span class="n">max_size</span><span class="o">=</span><span class="p">(</span><span class="mi">115</span><span class="p">,</span> <span class="mi">70</span><span class="p">),</span>
            <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">,</span>
            <span class="n">mask</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">425</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">520</span><span class="p">))</span>
        <span class="k">if</span> <span class="n">match</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">kb</span><span class="p">.</span><span class="nf">find_key</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">match</span><span class="p">.</span><span class="n">region</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s">"lowercase"</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span></code></pre>
</figure>

<p><a href="https://stb-tester.com/manual/python-api#stbt.find_selection_from_background">stbt.find_selection_from_background</a> compares the video frame (captured from
the device under test) against the specified reference image
(<em>“lowercase-background.png”</em>). This reference image is a screenshot of the
keyboard <strong>without any selection</strong>. Thus, any differences between the frame
(which <em>does</em> show a white rectangle around the selected key) and the reference
image are going to tell us where the selection is. If the differences span a
larger region than the size of the biggest key (<code class="language-plaintext highlighter-rouge">max_size</code> above), then it
means that we’re looking at a different screen — not the keyboard.</p>

<p>You may need to create this selection-less image by merging two different
screenshots together. This video shows how to do it in the <a href="https://www.gimp.org/">GNU Image
Manipulation Program</a>, a free open-source cross-platform image editor:</p>

<video class="screenshot" controls="true" poster="/blog/images/keyboard/lowercase-background.png">
  <source src="https://stb-tester.com/blog/images/keyboard/find_selection_from_background.mp4" />
</video>

<p>Step by step instructions:</p>

<ol>
  <li>Open both screenshots. They must have the selection on different,
non-overlapping keys.</li>
  <li>Drag one of the open image tabs onto the other tab and drop it into the
<em>Layers</em> window so that it’s above the existing layer.</li>
  <li>Use the rectangle selection tool to select the part of the image that
contains the selection.</li>
  <li>Choose <em>Edit &gt; Clear</em> (or press <em>Delete</em> on your keyboard). The layer
underneath will show through the deleted region, showing the same key but
without the white rectangle.</li>
  <li>Use <em>File &gt; Export As…</em> to save the image in PNG format to your test-pack.</li>
  <li>Don’t forget to commit the image to git! (Until you do, your IDE will show
a <a href="https://stb-tester.com/blog/2020/01/23/pylint-plugin">lint error</a> underneath the filename to remind you.)</li>
</ol>

<p>Now, to recognize all three modes, we need to create similar (selection-less)
reference images for the other modes: <em>“uppercase-background.png”</em> and
<em>“symbols-background.png”</em>. Finally, we update our <code class="language-plaintext highlighter-rouge">Search.selection</code> property
so that it looks like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python">    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">selection</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">mode</span> <span class="ow">in</span> <span class="p">[</span><span class="s">"lowercase"</span><span class="p">,</span> <span class="s">"uppercase"</span><span class="p">,</span> <span class="s">"symbols"</span><span class="p">]:</span>
            <span class="n">match</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">find_selection_from_background</span><span class="p">(</span>
                <span class="n">mode</span> <span class="o">+</span> <span class="s">"-background.png"</span><span class="p">,</span>
                <span class="n">max_size</span><span class="o">=</span><span class="p">(</span><span class="mi">115</span><span class="p">,</span> <span class="mi">70</span><span class="p">),</span>
                <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">,</span>
                <span class="n">mask</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">125</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">425</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">520</span><span class="p">))</span>
            <span class="k">if</span> <span class="n">match</span><span class="p">:</span>
                <span class="k">return</span> <span class="n">kb</span><span class="p">.</span><span class="nf">find_key</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">match</span><span class="p">.</span><span class="n">region</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">)</span>

        <span class="k">return</span> <span class="bp">None</span></code></pre>
</figure>

<div class="bs-callout bs-callout-default">
  <h4 id="tip">Tip</h4>

  <p>You can visualise &amp; debug your Page Object’s properties in the <strong>Object
Repository</strong> tab of your Stb-tester Portal:</p>

  <figure class="text-center">
  <img src="https://stb-tester.com/blog/images/keyboard/object-repository.png" class="border" />
  <figcaption>
    Our <em>Search</em> page object in Stb-tester's object repository.
  </figcaption>
</figure>

  <p>To learn more about Page Objects see <a href="https://stb-tester.com/manual/object-repository">Object Repository</a> in the Stb-tester
manual, and the <a href="https://stb-tester.com/manual/python-api#stbt.FrameObject">stbt.FrameObject</a> API reference documentation.</p>
</div>

<h3 id="navigating-the-keyboard">Navigating the keyboard</h3>

<p>Now we have all the pieces we need to navigate the on-screen keyboard:</p>

<ul>
  <li>A way to tell which button is currently selected.</li>
  <li>A graph that tells us the shortest path from each key to any other key.</li>
</ul>

<p>We’ll add a method to our <code class="language-plaintext highlighter-rouge">Search</code> Page Object called <code class="language-plaintext highlighter-rouge">enter_text</code>. This will
take a <code class="language-plaintext highlighter-rouge">text</code> parameter, and it will type the given text into the on-screen
keyboard by calling <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.enter_text">Keyboard.enter_text</a>:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">Search</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="p">...</span>

    <span class="k">def</span> <span class="nf">enter_text</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">kb</span><span class="p">.</span><span class="nf">enter_text</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">self</span><span class="p">)</span></code></pre>
</figure>

<figure class="pull-right">
  <video class="screenshot" controls="true">
    <source src="https://stb-tester.com/blog/images/keyboard/video.mp4" />
  </video>
</figure>

<p><a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.enter_text">Keyboard.enter_text</a> will use the <code class="language-plaintext highlighter-rouge">selection</code> property of its <code class="language-plaintext highlighter-rouge">page</code> parameter
to see which button is currently selected. Then it will loop over each letter
in <code class="language-plaintext highlighter-rouge">text</code>: Find a key in our model that matches that letter, navigate to it,
and press <code class="language-plaintext highlighter-rouge">KEY_OK</code> to type the letter.</p>

<div class="bs-callout bs-callout-default">
  <h4 id="keyname-versus-keytext">Key.name versus Key.text</h4>

  <p><a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.enter_text">Keyboard.enter_text</a> searches for a key with a <code class="language-plaintext highlighter-rouge">text</code> attribute that matches
the desired letter. A key’s <code class="language-plaintext highlighter-rouge">text</code> defaults to its <code class="language-plaintext highlighter-rouge">name</code> if the name is a
single character. This heuristic makes it convenient to specify most keyboards;
longer names like “CLEAR” typically don’t enter any text when pressed. If you
do have a key that types longer text when pressed (for example “@gmail.com”)
you can add it like this:</p>

  <figure class="highlight">
    <pre><code class="language-python" data-lang="python"><span class="n">kb</span><span class="p">.</span><span class="nf">add_key</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">"@gmail.com"</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="s">"@gmail.com"</span><span class="p">,</span>
           <span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(...),</span> <span class="n">mode</span><span class="o">=</span><span class="p">...)</span></code></pre>
  </figure>

  <p>Or if it’s part of a grid, you can specify it like this:</p>

  <figure class="highlight">
    <pre><code class="language-python" data-lang="python"><span class="n">kb</span><span class="p">.</span><span class="nf">add_grid</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Grid</span><span class="p">(</span>
    <span class="n">region</span><span class="o">=</span><span class="p">...,</span>
    <span class="n">data</span><span class="o">=</span><span class="p">[</span>
        <span class="p">[{</span><span class="s">"name"</span><span class="p">:</span> <span class="s">"@gmail.com"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">:</span> <span class="s">"@gmail.com"</span><span class="p">},</span> <span class="p">{</span><span class="s">"name"</span><span class="p">:</span> <span class="s">"another key"</span><span class="p">,</span> <span class="p">...}],</span>
        <span class="p">...</span>
    <span class="p">]))</span></code></pre>
  </figure>

  <p>Some of the entries in <code class="language-plaintext highlighter-rouge">data</code> can be dicts like the example above and others
can be strings, like we had seen in earlier examples — but remember that a
<a href="https://stb-tester.com/manual/python-api#stbt.Grid">stbt.Grid</a> is only suitable if all the cells in the grid are the same size.
For irregular-shaped keyboards you might have to specify each key (and its
region) using <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_key">Keyboard.add_key</a>.</p>
</div>

<p>We can also navigate to a single key using <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.navigate_to">Keyboard.navigate_to</a>. For example,
here is the implementation of a <code class="language-plaintext highlighter-rouge">clear</code> method that we can provide so that our
test scripts can clear any text that has been entered into the Search page:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">Search</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="p">...</span>

    <span class="k">def</span> <span class="nf">clear</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">kb</span><span class="p">.</span><span class="nf">navigate_to</span><span class="p">(</span><span class="s">"CLEAR"</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">self</span><span class="p">)</span>
        <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_OK"</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">refresh</span><span class="p">()</span></code></pre>
</figure>

<p>Some keyboards have an explicit “SEARCH” button that you have to press after
typing the text. For those keyboards, our Page Object’s <code class="language-plaintext highlighter-rouge">enter_text</code> method
would look like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python">    <span class="k">def</span> <span class="nf">enter_text</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">self</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">kb</span><span class="p">.</span><span class="nf">enter_text</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">page</span><span class="p">)</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">kb</span><span class="p">.</span><span class="nf">navigate_to</span><span class="p">(</span><span class="s">"SEARCH"</span><span class="p">,</span> <span class="n">page</span><span class="p">)</span>
        <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_OK"</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">page</span><span class="p">.</span><span class="nf">refresh</span><span class="p">()</span></code></pre>
</figure>

<h3 id="common-mistake-using-an-outdated-page-instance">Common mistake: Using an outdated page instance</h3>

<p><a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.enter_text">Keyboard.enter_text</a> and <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.navigate_to">Keyboard.navigate_to</a> take a Page Object instance in
their <code class="language-plaintext highlighter-rouge">page</code> parameter. This instance has a <code class="language-plaintext highlighter-rouge">selection</code> property that reflects
the position of the selection <strong>at the time the instance was created</strong>. If this
instance is out of date (because the selection has moved since that time), then
<code class="language-plaintext highlighter-rouge">stbt.Keyboard</code> will calculate a path from the wrong start position to your
target node.</p>

<p>This is because Stb-tester’s Page Objects are immutable: An instance of the
Page Object reflects the state of the device-under-test at the time the
instance was created.</p>

<p>The following code won’t work:</p>

<figure class="highlight bad-example">
  <pre><code class="language-python" data-lang="python">    <span class="c1"># EXAMPLE OF BAD CODE -- DON'T COPY
</span>    <span class="k">def</span> <span class="nf">enter_text</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
        <span class="n">kb</span><span class="p">.</span><span class="nf">enter_text</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">self</span><span class="p">)</span>
        <span class="n">kb</span><span class="p">.</span><span class="nf">navigate_to</span><span class="p">(</span><span class="s">"SEARCH"</span><span class="p">,</span> <span class="n">page</span><span class="o">=</span><span class="n">self</span><span class="p">)</span>  <span class="c1"># &lt;-- self.selection is outdated!
</span>        <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_OK"</span><span class="p">)</span></code></pre>
</figure>

<p>To get the latest state, you can create a <strong>new</strong> instance of the Page Object
like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">page</span> <span class="o">=</span> <span class="nc">Search</span><span class="p">()</span></code></pre>
</figure>

<p>Or like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">page</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">refresh</span><span class="p">()</span></code></pre>
</figure>

<p>(where <code class="language-plaintext highlighter-rouge">self</code> is an instance of our <code class="language-plaintext highlighter-rouge">Search</code> Page Object.)</p>

<p>For this purpose, <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.enter_text">Keyboard.enter_text</a> and <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.navigate_to">Keyboard.navigate_to</a>
return a new page instance that reflects the state of the device-under-test
<strong>after</strong> the text has been entered (or the navigation completed). We can use
their return value instead of calling <code class="language-plaintext highlighter-rouge">self.refresh()</code>. Here’s the corrected
example:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python">    <span class="c1"># FIXED EXAMPLE
</span>    <span class="k">def</span> <span class="nf">enter_text</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">self</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">kb</span><span class="p">.</span><span class="nf">enter_text</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">page</span><span class="p">)</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">kb</span><span class="p">.</span><span class="nf">navigate_to</span><span class="p">(</span><span class="s">"SEARCH"</span><span class="p">,</span> <span class="n">page</span><span class="p">)</span>
        <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_OK"</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">page</span><span class="p">.</span><span class="nf">refresh</span><span class="p">()</span></code></pre>
</figure>

<p>Note that we have made our method return an updated page instance. This is
consistent with <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.enter_text">Keyboard.enter_text</a>’s behaviour, and it allows any testcases
that call our <code class="language-plaintext highlighter-rouge">enter_text</code> method to use the same pattern.</p>

<h3 id="simple-keyboards-no-modes">Simple keyboards (no modes)</h3>

<p>Many on-screen keyboards don’t have modes — there’s just a single, uppercase
or lowercase keyboard. Or maybe your keyboard does have several modes, but you
don’t care to test them — you just want your test script to type in a search
term, and the case doesn’t matter. (Typically the different modes are only
really necessary for login keyboards where you type a password.)</p>

<p>In this case, you don’t need to specify <code class="language-plaintext highlighter-rouge">mode</code> when you call
<a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_key">Keyboard.add_key</a>, <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_grid">Keyboard.add_grid</a>, or <a href="https://stb-tester.com/manual/python-api#stbt.Keyboard.add_transition">Keyboard.add_transition</a>. The
key’s name alone, or the name + region, will be enough to identify a key
unambiguously. You will need to make your <code class="language-plaintext highlighter-rouge">enter_text</code> method convert to
lowercase or uppercase, as appropriate, like this:</p>

<figure class="highlight simple-kb">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">Search</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="p">...</span>

    <span class="k">def</span> <span class="nf">enter_text</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">kb</span><span class="p">.</span><span class="nf">enter_text</span><span class="p">(</span><span class="n">text</span><span class="p">.</span><span class="nf">lower</span><span class="p">(),</span> <span class="n">page</span><span class="o">=</span><span class="n">self</span><span class="p">)</span></code></pre>
</figure>

<h3 id="shift-modes">Shift modes</h3>

<p>If your keyboard has a “shift” mode, where pressing <code class="language-plaintext highlighter-rouge">KEY_OK</code> on an uppercase
letter types the letter <strong>and</strong> changes to the lowercase mode, you can model
this by specifying a transition from every key in the uppercase mode, to the
same key (by region) in the lowercase mode. The code would look somewhat
similar to the example <a href="#figure6">earlier in this article</a> for changing modes
by pressing <code class="language-plaintext highlighter-rouge">KEY_PLAY</code>.</p>

<p>See the full code from this tutorial
<a href="https://github.com/stb-tester/tutorial-test-pack/blob/keyboard-v32/tests/youtube/pages/search/search.py">here</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester v32 released: Better on-screen keyboard support, new API to find
selections, and more.
</title>
        <pubDate>Fri, 02 Oct 2020 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2020/10/02/stb-tester-v32-released</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2020/10/02/stb-tester-v32-released</guid>
        <description>
          &lt;p&gt;Version 32 of the stb-tester core Python API is now available. We have
re-architected &lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.Keyboard&quot;&gt;stbt.Keyboard&lt;/a&gt;&lt;/em&gt; to easily test on-screen keyboards with
multiple modes (such as lowercase, uppercase, and symbols modes). We have added
a powerful new API called &lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.find_selection_from_background&quot;&gt;stbt.find_selection_from_background&lt;/a&gt;&lt;/em&gt; to easily
detect the position of a “selection” or “highlight” on the screen. We have sped
up &lt;em&gt;&lt;a href=&quot;https://stb-tester.com/manual/python-api#stbt.ocr&quot;&gt;stbt.ocr&lt;/a&gt;&lt;/em&gt; hugely by caching previous results for identical inputs — so
now you can use OCR more freely in navigation code without worrying about
slowing down your test scripts.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Version 32 of the stb-tester core Python API is now available. We have
re-architected <em><a href="https://stb-tester.com/manual/python-api#stbt.Keyboard">stbt.Keyboard</a></em> to easily test on-screen keyboards with
multiple modes (such as lowercase, uppercase, and symbols modes). We have added
a powerful new API called <em><a href="https://stb-tester.com/manual/python-api#stbt.find_selection_from_background">stbt.find_selection_from_background</a></em> to easily
detect the position of a “selection” or “highlight” on the screen. We have sped
up <em><a href="https://stb-tester.com/manual/python-api#stbt.ocr">stbt.ocr</a></em> hugely by caching previous results for identical inputs — so
now you can use OCR more freely in navigation code without worrying about
slowing down your test scripts.</p>

<p>The are several other new APIs and many fixes and quality-of-life improvements.
Upgrading should be painless for most users; the only backwards-incompatible
change that will affect most users is with <em>stbt.Keyboard</em>. A detailed tutorial
explaining the new API is available here: <a href="https://stb-tester.com/blog/2020/10/02/testing-onscreen-keyboards">Testing on-screen keyboards with
Stb-tester</a>.</p>

<p>For more details see the <a href="https://stb-tester.com/manual/release-notes"><strong>v32 release notes</strong></a>.</p>

<div class="bs-callout bs-callout-default">
  <h4 id="note">Note:</h4>

  <p>If you have updated your <code class="language-plaintext highlighter-rouge">.stbt.conf</code> file to use the new version but you get
the error message <em>“Invalid test_pack.stbt_version value set in your .stbt.conf
file. It is currently ‘32’ but must be an integer between 29 and 31”</em>, please
contact <a href="mailto:support@stb-tester.com">support@stb-tester.com</a> to schedule a firmware update for your
Stb-tester Nodes.</p>
</div>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester allows you to test set-top boxes remotely</title>
        <pubDate>Thu, 30 Apr 2020 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2020/04/30/testing-stb-remotely</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2020/04/30/testing-stb-remotely</guid>
        <description>
          &lt;p&gt;Stb-tester’s cloud-based portal gives you remote access to all the set-top
boxes and OTT devices in your test-farm, from anywhere in the world. Many of
our customers are using this capability to allow their staff, who are in
quarantine during the current coronavirus pandemic, to continue QA activities
from home — both manual &amp;amp; automated testing.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester’s cloud-based portal gives you remote access to all the set-top
boxes and OTT devices in your test-farm, from anywhere in the world. Many of
our customers are using this capability to allow their staff, who are in
quarantine during the current coronavirus pandemic, to continue QA activities
from home — both manual &amp; automated testing.</p>

<blockquote>
  <p>“STB-Tester has made it possible for us to provide test facilities for
    colleagues in India, as well as in Belgium, who are stuck at home because
    of the coronavirus crisis. I can't stress enough how crucial this
    STB-Tester infrastructure is for us nowadays. Our colleagues rely on it for
    being able to continue their work.”
  </p>
  <footer>
    Karel Van Daele, Team lead, Engineering team, Proximus TV (Belgacom)
  </footer>
</blockquote>

<figure class="pull-right" style="width:60%">
  <img src="https://stb-tester.com/blog/images/manual-control.png" />
  <figcaption>
    Live video and remote control in the Stb-tester Portal
  </figcaption>
</figure>

<p>In the Stb-tester portal you can see a live video stream from each set-top box
and you can press buttons on the remote control. All you need is a web browser.
This doesn’t require a VPN nor a complex firewall setup.</p>

<p>Stb-tester stores an HD video recording of each test that is executed. This
aids clear communication: a defect is much easier to explain if you can share
a video demonstrating it.</p>

<p>The set-top boxes themselves can also be located anywhere: Some in the test-lab
at your office, some at home, on different networks. This allows testing the
set-top box’s behaviour on different real-world network conditions. The
physical setup is very easy: Just connect an <a href="https://stb-tester.com/solutions">Stb-tester Node</a> to the network
(so that it can reach the Stb-tester Portal) and to the device that you will be
testing.</p>

<p>For more details see <a href="https://stb-tester.com/blog/2018/01/17/announcing-the-stb-tester-cloud-platform">Announcing the Stb-tester Cloud Platform</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Announcing Stb-tester integration with GitHub Enterprise</title>
        <pubDate>Thu, 06 Feb 2020 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2020/02/06/github-enterprise</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2020/02/06/github-enterprise</guid>
        <description>
          &lt;p&gt;Stb-tester now provides tight integration with your organisation’s own
&lt;a href=&quot;https://github.com/enterprise&quot;&gt;GitHub Enterprise&lt;/a&gt; instance.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester now provides tight integration with your organisation’s own
<a href="https://github.com/enterprise">GitHub Enterprise</a> instance.</p>

<p>If you use GitHub Enterprise, the Stb-tester Portal can integrate with a
test-pack (git repository of test scripts) hosted on your GitHub Enterprise
instance to provide the following features (also available for private git
repositories hosted on <a href="https://github.com">github.com</a>):</p>

<ul>
  <li>Transparently sync code from GitHub Enterprise to the Stb-tester Portal and
to the Stb-tester Nodes that execute the tests.</li>
  <li>Transparently sync code from your IDE during test development.</li>
  <li>Record the exact version of the test scripts that were used in each test-run.</li>
  <li>Run tests from any git branch.</li>
  <li>Interactive test-development tools on the Stb-tester Portal, such as the
<a href="https://stb-tester.com/manual/object-repository">Object Repository</a>.</li>
</ul>

<p>Furthermore, using GitHub Enterprise allows you to log into the Stb-tester
Portal using your organisation’s existing Single Sign-On mechanism — any
users that are authorised to access the test-pack git repository, will
automatically be authorised to access the Stb-tester Portal. You don’t need to
manually manage a separate set of user accounts or permissions.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Visual Studio Code integration with the Stb-tester Portal</title>
        <pubDate>Tue, 28 Jan 2020 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2020/01/28/visual-studio-code</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2020/01/28/visual-studio-code</guid>
        <description>
          &lt;p&gt;Stb-tester provides deep integration with Visual Studio Code, the world’s most
popular IDE.&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; You can now execute tests remotely (on your Stb-tester Node
hardware) right from your IDE. This can greatly speed up your test-development
cycle.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;According to Stack Overflow’s 2019 [developer survey]. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester provides deep integration with Visual Studio Code, the world’s most
popular IDE.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> You can now execute tests remotely (on your Stb-tester Node
hardware) right from your IDE. This can greatly speed up your test-development
cycle.</p>

<figure class="m-b-2">
  <img src="https://stb-tester.com/blog/images/vscode/run-test.png" class="border" alt="'Run Test' button in VS Code, with test logs in the Output window" />
  <figcaption>
    Run a test directly from VS Code
  </figcaption>
</figure>

<p>Clicking the “Run Test” button will automatically perform the following steps:</p>

<ul>
  <li>Sync your code (including local uncommitted changes) to the Stb-tester
Portal. This uses a temporary git branch so it doesn’t change your local
workspace and it doesn’t affect other users.</li>
  <li>Run the test remotely on your chosen Stb-tester Node.</li>
  <li>Show the test’s output in VS Code’s Output window.</li>
</ul>

<p>This is useful during test-development, when you are writing or debugging the
test. It greatly speeds up the development cycle because you don’t need to sync
the code with a git commit &amp; git push; and you don’t need to switch to the
Stb-tester Portal in your browser to select the test or view the logs.</p>

<p>After you are satisfied with your changes, you still need to do a git commit &amp;
git push to share your new test with the rest of your team — see our
recommended <a href="https://stb-tester.com/manual/workflow">Development Workflow</a>.</p>

<p>To set up this functionality in VS Code, see <a href="https://stb-tester.com/manual/ide-configuration">IDE Configuration</a> in the
Stb-tester manual.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>According to Stack Overflow’s 2019 <a href="https://insights.stackoverflow.com/survey/2019#development-environments-and-tools">developer survey</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Static analysis for your test scripts: Catching common mistakes before you
run the tests!
</title>
        <pubDate>Thu, 23 Jan 2020 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2020/01/23/pylint-plugin</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2020/01/23/pylint-plugin</guid>
        <description>
          &lt;p&gt;&lt;a href=&quot;https://www.pylint.org/&quot;&gt;Pylint&lt;/a&gt; is a static analysis tool for Python code. This means that it analyses
your code to catch common mistakes, without actually running your code. This
saves a &lt;em&gt;lot&lt;/em&gt; of development time by catching the mistakes immediately —your
IDE shows red squiggles under each mistake— instead of running the test and
waiting for it to reach the line of code where the mistake is.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p><a href="https://www.pylint.org/">Pylint</a> is a static analysis tool for Python code. This means that it analyses
your code to catch common mistakes, without actually running your code. This
saves a <em>lot</em> of development time by catching the mistakes immediately —your
IDE shows red squiggles under each mistake— instead of running the test and
waiting for it to reach the line of code where the mistake is.</p>

<p>Stb-tester provides a Pylint plugin that teaches Pylint about some mistakes
that are specific to Stb-tester APIs. For example, the <em>frame</em> parameter of
<a href="https://stb-tester.com/manual/python-api#stbt.match">stbt.match</a> is usually optional, but it’s mandatory when called inside a
FrameObject property:</p>

<figure class="m-b-2">
  <img src="https://stb-tester.com/blog/images/vscode/stbt-frame-object-missing-frame.png" alt="stbt.match('home.png') missing 'frame' argument - pylint(stbt-frame-object-missing-frame)" />
  <figcaption>
    VS Code showing an error caught by static analysis
  </figcaption>
</figure>

<p>Another common mistake is using a reference image that doesn’t exist on disk,
or that hasn’t been committed to git:</p>

<figure class="m-b-2">
  <img src="https://stb-tester.com/blog/images/vscode/stbt-uncommitted-image.png" alt="Image 'tests/appletv/home/YouTube.png' not committed to git - pylint(stbt-uncommitted-image)" />
  <figcaption>
    Forgetting to commit the reference image is a frustrating mistake — but
    not if you catch it immediately!
  </figcaption>
</figure>

<p>Some Stb-tester APIs raise an exception on failure (for example
<a href="https://stb-tester.com/manual/python-api#stbt.wait_for_match">stbt.wait_for_match</a>) but others don’t (for example <a href="https://stb-tester.com/manual/python-api#stbt.match">stbt.match</a> and
<a href="https://stb-tester.com/manual/python-api#stbt.wait_until">stbt.wait_until</a>, among others). This can be confusing, so we report an error
if you forget to use the return value from the latter group of APIs:</p>

<figure class="m-b-2">
  <img src="https://stb-tester.com/blog/images/vscode/stbt-unused-return-value.png" alt="'stbt.wait_until' return value not used (missing 'assert'?) - pylint(stbt-unused-return-value)" />
  <figcaption>
    If you don't check the return value from <i>stbt.wait_until</i>, it's
    probably a mistake
  </figcaption>
</figure>

<h3 id="disabling-pylint-warnings">Disabling Pylint warnings</h3>

<p>In some rare cases you might want to ignore the return value for a valid
reason. You can disable the warning for that specific line, by adding a
comment like this:</p>

<figure class="m-b-2">
  <img src="https://stb-tester.com/blog/images/vscode/pylint-disable.png" alt="# pylint:disable=stbt-unused-return-value" />
  <figcaption>
    Disable the warning on this line (but do warn me if I forget to check the
    return value in other places!)
  </figcaption>
</figure>

<p>Some of Pylint’s built-in checks are related to coding style, not real
problems. If they get annoying you can disable specific checks by listing them
in the <code class="language-plaintext highlighter-rouge">.pylintrc</code> file in the root of your test-pack. For example:</p>

<figure class="highlight">
  <pre><code class="language-none" data-lang="none">[MESSAGES CONTROL]
disable=
  line-too-long,
  missing-docstring,</code></pre>
</figure>

<p>The name of each checker (such as “line-too-long”) is shown after the error
message in your IDE.</p>

<h3 id="ide-configuration">IDE Configuration</h3>

<p>To set up your IDE to catch all of these errors, see <a href="https://stb-tester.com/manual/ide-configuration">IDE Configuration</a> in the
Stb-tester manual.</p>

<p>If you’re curious about the full list of checks that Stb-tester adds to Pylint,
see the source code of our Pylint plugin <a href="https://github.com/stb-tester/stb-tester/blob/master/_stbt/pylint_plugin.py">here</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Python&apos;s for...else: Surprisingly useful for implementing retry logic
in test scripts
</title>
        <pubDate>Thu, 05 Dec 2019 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2019/12/05/python-for-else</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2019/12/05/python-for-else</guid>
        <description>
          &lt;p&gt;The optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;else&lt;/code&gt; clause after a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt; loop is an obscure Python feature that
is surprisingly useful when writing automated GUI tests.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>The optional <code class="language-plaintext highlighter-rouge">else</code> clause after a <code class="language-plaintext highlighter-rouge">for</code> loop is an obscure Python feature that
is surprisingly useful when writing automated GUI tests.</p>

<p>In our test scripts we often need to look for something by navigating through a
menu. For example, let’s say we’re looking for an app on the Apple TV’s “running
apps” view, so that we can quit it:</p>

<figure class="text-center">
  <img src="https://stb-tester.com/blog/images/for-else/appletv.png" />
  <figcaption>
    Apple TV's “running apps” view, with app name circled in red
  </figcaption>
</figure>

<p>We have a <a href="https://stb-tester.com/manual/object-repository#what-is-a-page-object">Page Object</a> called <em>AppSwitcher</em> that knows how to read the app
name from this view (circled in red above). The following Python code presses
the <em>LEFT</em> button on the remote control until it finds the desired app (up to
20 times) and then it kills the app by double-clicking the <em>UP</em> button:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="code"><pre><span class="k">def</span> <span class="nf">kill_app</span><span class="p">(</span><span class="n">app_name</span><span class="p">):</span>
    <span class="nf">double_click</span><span class="p">(</span><span class="s">"KEY_TV"</span><span class="p">)</span>  <span class="c1"># Open the "running apps" view
</span>    <span class="n">page</span> <span class="o">=</span> <span class="nf">wait_until</span><span class="p">(</span><span class="n">appletv</span><span class="p">.</span><span class="n">AppSwitcher</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">page</span><span class="p">,</span> <span class="s">'Failed to open the "running apps" view'</span>

    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">20</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">page</span><span class="p">.</span><span class="n">title</span> <span class="o">==</span> <span class="n">app_name</span><span class="p">:</span>
            <span class="c1"># Kill the app by double-clicking KEY_UP:
</span>            <span class="nf">double_click</span><span class="p">(</span><span class="s">"KEY_UP"</span><span class="p">)</span>
            <span class="k">return</span>
        <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_LEFT"</span><span class="p">)</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">page</span><span class="p">.</span><span class="nf">refresh</span><span class="p">()</span>

    <span class="k">assert</span> <span class="bp">False</span><span class="p">,</span> <span class="s">"Didn't find app '%s'"</span> <span class="o">%</span> <span class="p">(</span><span class="n">app_name</span><span class="p">,)</span>
</pre></td></tr></tbody></table></code></pre>
</figure>

<p>Once it finds &amp; kills the desired app, this code returns (on line 10) so it
doesn’t keep on pressing <em>LEFT</em> looking for other apps. The <code class="language-plaintext highlighter-rouge">assert False</code>
statement on line 14 only runs if you didn’t find the target app and didn’t
return early from the loop.</p>

<h3 id="but-what-if-you-want-to-exit-the-loop-without-returning-from-the-function">But what if you want to exit the loop without returning from the function?</h3>

<p>Sometimes it isn’t convenient to put each <code class="language-plaintext highlighter-rouge">for</code> loop into its own function.
Instead of <code class="language-plaintext highlighter-rouge">return</code> we can use <code class="language-plaintext highlighter-rouge">break</code>, and we can put our error-handling in an
<code class="language-plaintext highlighter-rouge">else</code> clause.</p>

<p>Here’s the <a href="https://docs.python.org/3.6/reference/compound_stmts.html#the-for-statement">official Python documentation</a> on the <code class="language-plaintext highlighter-rouge">for</code> statement:</p>

<blockquote>
  <p>When the items are exhausted, the suite in the <code class="language-plaintext highlighter-rouge">else</code> clause, if present, is
executed, and the loop terminates.</p>

  <p>A <code class="language-plaintext highlighter-rouge">break</code> statement executed in the first suite terminates the loop without
executing the <code class="language-plaintext highlighter-rouge">else</code> clause’s suite.</p>
</blockquote>

<p>Our previous example would look like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="code"><pre>    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">20</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">page</span><span class="p">.</span><span class="n">title</span> <span class="o">==</span> <span class="n">app_name</span><span class="p">:</span>
            <span class="c1"># Kill the app by double-clicking KEY_UP:
</span>            <span class="nf">double_click</span><span class="p">(</span><span class="s">"KEY_UP"</span><span class="p">)</span>
            <span class="k">break</span>
        <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_LEFT"</span><span class="p">)</span>
        <span class="n">page</span> <span class="o">=</span> <span class="n">page</span><span class="p">.</span><span class="nf">refresh</span><span class="p">()</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">assert</span> <span class="bp">False</span><span class="p">,</span> <span class="s">"Didn't find app '%s'"</span> <span class="o">%</span> <span class="p">(</span><span class="n">app_name</span><span class="p">,)</span>

    <span class="c1"># Here I can do other stuff before returning</span>
</pre></td></tr></tbody></table></code></pre>
</figure>

<p>Note that <code class="language-plaintext highlighter-rouge">assert False</code> on line 9 only runs if we didn’t break from the loop.</p>

<p>Line 11 always gets run — it doesn’t matter if the loop ran all 20 times, or
if it exited early due to the <code class="language-plaintext highlighter-rouge">break</code> statement.</p>

<h3 id="an-example-of-retry-logic-handling-an-unknown-number-of-dialogs">An example of retry logic: Handling an unknown number of dialogs</h3>

<p>When testing playback on a video-streaming app (specifically, BBC iPlayer) we
needed to handle a series of dialogs before the content starts playing —
anywhere between 0 and 4 dialogs:</p>

<figure class="text-center">
  <img src="https://stb-tester.com/blog/images/for-else/iplayer-dialog.png" />
  <figcaption>
    One of iPlayer's many dialogs
  </figcaption>
</figure>

<p>Our test script to handle this is shown below. If we see the “Resume” dialog
above, we’ll select “Start from the beginning”. We loop (retry) several times
so that we can handle any other dialogs that might appear, and we break from
the loop when we detect that content playback has started.</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
</pre></td><td class="code"><pre><span class="k">def</span> <span class="nf">press_ok_to_start_content</span><span class="p">():</span>
    <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">"KEY_OK"</span><span class="p">)</span>

    <span class="c1"># Handle any dialogs that may appear:
</span>    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
        <span class="n">page</span> <span class="o">=</span> <span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nc">Content</span><span class="p">()</span> <span class="ow">or</span> <span class="nc">Dialog</span><span class="p">())</span>
        <span class="k">assert</span> <span class="n">page</span><span class="p">,</span> <span class="s">"Failed to detect content start or dialog"</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"Page is a %s"</span> <span class="o">%</span> <span class="p">(</span><span class="nf">type</span><span class="p">(</span><span class="n">page</span><span class="p">),))</span>

        <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">page</span><span class="p">,</span> <span class="n">Dialog</span><span class="p">):</span>

            <span class="k">if</span> <span class="n">page</span><span class="p">.</span><span class="n">selection</span> <span class="ow">in</span> <span class="p">[</span>
                    <span class="s">"I am aged 16 or older"</span><span class="p">,</span>
                    <span class="s">"I understand, continue"</span><span class="p">,</span>
                    <span class="s">"Start from the beginning"</span><span class="p">,</span>
                    <span class="s">"Watch from Start"</span><span class="p">,</span>
                    <span class="s">"Watch Live"</span><span class="p">,</span>
                    <span class="s">"I have a TV Licence. Watch now."</span><span class="p">,</span>
                    <span class="s">"Continue without setting up lock"</span><span class="p">]:</span>

                <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_OK"</span><span class="p">)</span>

            <span class="k">elif</span> <span class="n">page</span><span class="p">.</span><span class="n">selection</span> <span class="ow">in</span> <span class="p">[</span>
                    <span class="s">"Resume"</span><span class="p">,</span>
                    <span class="s">"Cancel"</span><span class="p">,</span>
                    <span class="s">"I don't have a TV Licence."</span><span class="p">,</span>
                    <span class="s">"Set up Parental Guidance lock"</span><span class="p">]:</span>

                <span class="n">stbt</span><span class="p">.</span><span class="nf">press_and_wait</span><span class="p">(</span><span class="s">"KEY_DOWN"</span><span class="p">)</span>

            <span class="k">else</span><span class="p">:</span>
                <span class="k">assert</span> <span class="bp">False</span><span class="p">,</span> <span class="s">"Unknown dialog %r"</span> <span class="o">%</span> <span class="p">(</span><span class="n">page</span><span class="p">,)</span>

        <span class="k">else</span><span class="p">:</span>  <span class="c1"># Content
</span>            <span class="k">break</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">assert</span> <span class="bp">False</span><span class="p">,</span> <span class="s">"Didn't get to content after dismissing 5 dialogs"</span>

    <span class="c1"># Check playback:
</span>    <span class="k">assert</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">wait_for_motion</span><span class="p">(</span><span class="n">mask</span><span class="o">=</span><span class="s">"content-spinner-mask.png"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre>
</figure>

<p>(Note: <em>Content</em> and <em>Dialog</em> are <a href="https://stb-tester.com/manual/object-repository#what-is-a-page-object">Page Objects</a> that know how to
recognize the content playback screen and the dialog screens, respectively.
<em>Dialog</em> has a property called <em>selection</em> that reads the text of the selected
button — for example “Resume” in the screenshot above).</p>

<h3 id="why-not-use-a-while-loop">Why not use a while loop?</h3>

<p>We could have written the above loop like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="code"><pre>    <span class="n">page</span> <span class="o">=</span> <span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nc">Content</span><span class="p">()</span> <span class="ow">or</span> <span class="nc">Dialog</span><span class="p">())</span>
    <span class="k">assert</span> <span class="n">page</span><span class="p">,</span> <span class="s">"Failed to detect content start or dialog"</span>

    <span class="c1"># Handle any dialogs that may appear:
</span>    <span class="k">while</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">page</span><span class="p">,</span> <span class="n">Dialog</span><span class="p">):</span>
        <span class="c1"># ... Dialog-handling code here (not shown), and then:
</span>
        <span class="n">page</span> <span class="o">=</span> <span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nc">Content</span><span class="p">()</span> <span class="ow">or</span> <span class="nc">Dialog</span><span class="p">())</span>
        <span class="k">assert</span> <span class="n">page</span><span class="p">,</span> <span class="s">"Failed to detect content start or dialog"</span>

    <span class="c1"># Check playback:
</span>    <span class="k">assert</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">wait_for_motion</span><span class="p">(</span><span class="n">mask</span><span class="o">=</span><span class="s">"content-spinner-mask.png"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre>
</figure>

<p>But Python’s <code class="language-plaintext highlighter-rouge">while</code> loops have 2 problems:</p>

<ol>
  <li>
    <p>We have to repeat the code that updates <code class="language-plaintext highlighter-rouge">page</code> twice: In lines 1-2 and lines
8-9. It’s more boilerplate.</p>
  </li>
  <li>
    <p>Bugs in the test script or in the system-under-test can lead to an infinite
loop. We would have to add explicit timeout checks inside the loop — more
boilerplate.</p>
  </li>
</ol>

<p>The for…else pattern allows us to add assertions to our retry logic, to
handle the error case with a minimal amount of extra code.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>IDE Configuration: Smart code completion &amp; linting for Stb-tester APIs
</title>
        <pubDate>Mon, 02 Dec 2019 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2019/12/02/intellisense</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2019/12/02/intellisense</guid>
        <description>
          &lt;p&gt;The Stb-tester Python APIs are now available on PyPI (the Python Package Index)
so that you can install them on your development PC — it doesn’t matter if
you’re using Windows, MacOS, or Linux.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>The Stb-tester Python APIs are now available on PyPI (the Python Package Index)
so that you can install them on your development PC — it doesn’t matter if
you’re using Windows, MacOS, or Linux.</p>

<p>Installing this package will enable your IDE’s “IntelliSense” or “Code Insight”
features such as code completion, type inference, inline documentation, and
linting.</p>

<figure>
  <img src="https://stb-tester.com/blog/images/pycharm/autocomplete.png" class="border" />
  <figcaption>
    Auto-completion suggestions in PyCharm
  </figcaption>
</figure>

<p>For installation instructions see <a href="https://stb-tester.com/manual/ide-configuration">IDE Configuration</a> in the Stb-tester manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Using the Object Repository to debug your Page Objects</title>
        <pubDate>Mon, 14 Oct 2019 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2019/10/14/object-repo-debug</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2019/10/14/object-repo-debug</guid>
        <description>
          &lt;p&gt;I have written a Page Object for the “Search” page in the YouTube app on
Apple TV. My Page Object has a property that reads the search term using OCR,
but the OCR is mis-reading some words. Let’s investigate!&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>I have written a Page Object for the “Search” page in the YouTube app on
Apple TV. My Page Object has a property that reads the search term using OCR,
but the OCR is mis-reading some words. Let’s investigate!</p>

<p>The page looks like this:</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/object-repo-debug/screenshot.png" /></p>
</figure>

<p>My Page Object’s <code class="language-plaintext highlighter-rouge">search_text</code> property reads the search term that the user has
typed using the on-screen keyboard. First we look for the magnifying-glass
icon, to determine the region where the text should appear:</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/object-repo-debug/icon.png" /></p>
</figure>

<p>Then we read the text to the right of the icon. Here is the code of my Page
Object property that does this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">Search</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="c1"># 'is_visible' and other properties not shown
</span>
    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">search_text</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">icon</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">"search-icon.png"</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">icon</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span>
                <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">,</span>
                <span class="n">region</span><span class="o">=</span><span class="n">icon</span><span class="p">.</span><span class="n">region</span><span class="p">.</span><span class="nf">right_of</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="mi">240</span><span class="p">))</span></code></pre>
</figure>

<p>One of my tests failed because the text didn’t match the value I was expecting.
First we save and commit a screenshot under <code class="language-plaintext highlighter-rouge">selftest/screenshots/</code>. (I
recommend that you organise the screenshots in subfolders according to device,
application, and Page Object, for example
<code class="language-plaintext highlighter-rouge">selftest/screenshots/appletv/youtube/keyboard/peppa pig.png</code>.)</p>

<p>Then we can debug the Page Object against this screenshot in Stb-tester’s
<strong><a href="https://stb-tester.com/manual/object-repository">Object Repository</a></strong>:</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/object-repo-debug/Denna piq.png" /></p>
</figure>

<p>In the Object Repository, your Page Object’s properties are run against the
committed screenshots, instead of a live video-frame from the device under test.
Here we can see that the Page Object’s <code class="language-plaintext highlighter-rouge">search_text</code> property is reading “Denna
piq” instead of “peppa pig”. To investigate, let’s click on the property’s
drop-down menu and select <strong>Debug</strong>:</p>

<figure class="text-center border">
  <p><img src="https://stb-tester.com/blog/images/object-repo-debug/debug-menu.png" /></p>
</figure>

<p>This will show detailed debug for each of the Stb-tester APIs that are called
to evaluate this property (<code class="language-plaintext highlighter-rouge">stbt.match</code> to find the magnifying-glass icon and
<code class="language-plaintext highlighter-rouge">stbt.ocr</code> to read the text). Now the problem is obvious: The region that we
are giving to <code class="language-plaintext highlighter-rouge">stbt.ocr</code> is chopping off the bottom of the text!</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/object-repo-debug/debug.png" /></p>
</figure>

<p>The solution is to extend the region downward by a few pixels so that it
includes the descenders in the text:</p>

<figure class="highlight">
  <pre><code class="language-diff" data-lang="diff">     @property
     def search_text(self):
         icon = stbt.match("search-icon.png", frame=self._frame)
         if icon:
             return stbt.ocr(
                 frame=self._frame,
<span class="gd">-                region=icon.region.right_of(width=240))
</span><span class="gi">+                region=icon.region.right_of(width=240).extend(bottom=5))</span></code></pre>
</figure>

<p>Now we can see in the object repository that <code class="language-plaintext highlighter-rouge">search_text</code> is correct:</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/object-repo-debug/peppa pig.png" /></p>
</figure>

<p><strong>Tip:</strong> When you’re trying out code changes to debug an issue like this, you
can use <strong>stbt_rig.py snapshot</strong> to sync the code from your IDE to the
Stb-tester Portal every time you press “Save”. See <a href="https://stb-tester.com/manual/ide-configuration#sync-the-code-from-your-ide-to-the-stb-tester-portal">IDE Configuration</a> in the
Stb-tester manual.</p>

<p>To learn more about Page Objects, see <a href="https://stb-tester.com/manual/object-repository">Object Repository</a> in the Stb-tester
manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Porting a test-pack to Python 3</title>
        <pubDate>Fri, 20 Sep 2019 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2019/09/20/porting-to-python3</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2019/09/20/porting-to-python3</guid>
        <description>
          &lt;p&gt;Stb-tester v31 adds support for test-scripts written in Python 3 (Python 2 is
still supported, too). To migrate an existing test-pack to Python 3, we
recommend the following steps.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester v31 adds support for test-scripts written in Python 3 (Python 2 is
still supported, too). To migrate an existing test-pack to Python 3, we
recommend the following steps.</p>

<p>Note that there is no immediate pressure to migrate existing Python 2
test-packs. <strong>Python 2 test-scripts will continue to work,</strong> even after Python
2’s official sunset date in January 2020.</p>

<ul>
  <li>
    <p>Coordinate across your team so that nobody is doing new development with
Python 2 until the porting to Python 3 is complete.</p>
  </li>
  <li>
    <p>Check out a new git branch called “python3”.</p>
  </li>
  <li>
    <p>Delete all Python files that you aren’t using, and delete any “dead code”
that you aren’t using inside the remaining files. All the test-scripts are
version-controlled with git, so there is no need to keep unused code – you
can always get it back from the git history if you really need to.</p>

    <p>Commit this deletion to git.</p>
  </li>
  <li>
    <p>Update <code class="language-plaintext highlighter-rouge">.stbt.conf</code> to contain the following in the <code class="language-plaintext highlighter-rouge">[test_pack]</code> section, and
commit the change:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[test_pack]
stbt_version = 31
python_version = 3
</code></pre></div>    </div>
  </li>
  <li>
    <p>If you install any third-party packages using the <a href="https://stb-tester.com/kb/custom-environment">config/setup/setup</a> script:</p>

    <ul>
      <li>Change <code class="language-plaintext highlighter-rouge">apt install python-&lt;package-name&gt;</code> to <code class="language-plaintext highlighter-rouge">apt install
python3-&lt;package-name&gt;</code>.</li>
      <li>Change <code class="language-plaintext highlighter-rouge">pip install &lt;package-name&gt;</code> to <code class="language-plaintext highlighter-rouge">pip3 install &lt;package-name&gt;</code>.</li>
    </ul>

    <p>…and commit the changes.</p>
  </li>
  <li>
    <p>Run <code class="language-plaintext highlighter-rouge">2to3</code> to translate all your Python files to Python 3. This is a script
that comes with Python. The name of the script might be <code class="language-plaintext highlighter-rouge">2to3</code> or <code class="language-plaintext highlighter-rouge">2to3-2.7</code>
depending on your OS. Run it like this:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2to3 --write tests/*.py
</code></pre></div>    </div>

    <p>If you have Python files in sub-directories under <code class="language-plaintext highlighter-rouge">tests/</code> you will need to
specify them too. You can get the full list of files by running this:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git ls-files "tests/*.py"
</code></pre></div>    </div>

    <p>Make sure that you only run each file <em>once</em> through <code class="language-plaintext highlighter-rouge">2to3</code>.</p>
  </li>
  <li>
    <p>Inspect those changes manually, and make sure you understand them. Commit the
changes to git.</p>
  </li>
  <li>
    <p>Push your branch, and create a pull request. Stb-tester automatically runs a
<strong>static analysis</strong> of the code in your pull request. This means that you can
check that your tests are valid Python 3 code, <strong>without even having to run
the tests!</strong></p>

    <p>Stb-tester shows the result of the static analysis as a green tick
<img src="https://stb-tester.com/manual/images/green-tick.png" alt="" /> or red cross
<img src="https://stb-tester.com/manual/images/red-cross.png" alt="" /> on your pull request. Click the red cross
to see a log with details of the failures.</p>
  </li>
  <li>
    <p>Fix the errors, commit, push, and check the new static analysis results in
the pull request. Repeat until the static analysis is green.</p>

    <p>Some of the static analysis checks are about coding style, not actual errors.
You can disable these checks if you want, by adding them to <code class="language-plaintext highlighter-rouge">.pylintrc</code> in
your test-pack (the name of each check is shown in the static analysis log).</p>
  </li>
  <li>
    <p>Finally run the tests to catch any remaining problems that aren’t caught
by the static analysis (if any).</p>
  </li>
  <li>
    <p>Merge the pull request to master.</p>
  </li>
</ul>

<p>For more details about pull requests, see <a href="https://stb-tester.com/manual/workflow">Development Workflow</a> in the
Stb-tester manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester v31 released: Python 3 support! On-screen-keyboard navigation!
And more!
</title>
        <pubDate>Thu, 19 Sep 2019 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2019/09/19/stb-tester-v31-python3</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2019/09/19/stb-tester-v31-python3</guid>
        <description>
          &lt;p&gt;Version 31 of the Stb-tester core Python API is available, with support for
test-scripts written in Python 3 (Python 2 is still supported, too).&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Version 31 of the Stb-tester core Python API is available, with support for
test-scripts written in Python 3 (Python 2 is still supported, too).</p>

<p>You must specify which Python version to use in your test-pack’s <code class="language-plaintext highlighter-rouge">.stbt.conf</code>
file like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[test_pack]
stbt_version = 31
python_version = 3
</code></pre></div></div>

<p>Valid values are “2” and “3”. The tests are run in an Ubuntu 18.04 environment,
so you get Python 2.7 or 3.6, specifically.</p>

<p>We recommend Python 3 for all new test-packs, but there is no immediate
pressure to migrate existing Python 2 test-packs. <strong>Python 2 test-scripts will
continue to work,</strong> even after Python 2’s official sunset date in January 2020.
To port an existing test-pack see <a href="https://stb-tester.com/blog/2019/09/20/porting-to-python3">Porting a test-pack to Python 3</a>.</p>

<p>This release also contains new APIs that make it much easier to test on-screen
keyboards. We’ll publish tutorials on this blog soon; in the meantime see the
<a href="https://stb-tester.com/manual/python-api#stbt.Keyboard">stbt.Keyboard</a> reference documentation.</p>

<p>As always, changes to the <code class="language-plaintext highlighter-rouge">stbt</code> core Python API are version-controlled. You
can specify the version you want to use in your <a href="https://stb-tester.com/manual/advanced-configuration#test-pack-stbt-version">.stbt.conf</a> file. This
allows you to upgrade in a controlled manner, and to test the upgrade on a
branch first.</p>

<p>This release contains many other improvements and bug-fixes. For full details
see the <a href="https://stb-tester.com/manual/release-notes">v31 release notes</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Using match transparency</title>
        <pubDate>Tue, 26 Feb 2019 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2019/02/26/transparency</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2019/02/26/transparency</guid>
        <description>
          &lt;p&gt;Stb-tester v30 includes a new feature: Searching for a reference image that has
some transparent pixels. This blog post describes three ways you can use this
new feature that makes writing test-scripts much easier than before.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester v30 includes a new feature: Searching for a reference image that has
some transparent pixels. This blog post describes three ways you can use this
new feature that makes writing test-scripts much easier than before.</p>

<h3 id="matching-buttons-with-unknown-text">Matching buttons with unknown text</h3>

<p>Take this keyboard for example:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/transparency/keyboard.png" class="border" />
  <figcaption>
    Screenshot captured from set-top box
  </figcaption>
</figure>

<p>We want to find the currently selected key. The selection is green, unlike the
rest of the keys, but each key looks different – they have a different letter
on the inside. With transparency we can have a single reference image that will
match this selection no matter which key is selected.</p>

<p>In the video below we use the <a href="https://www.gimp.org/">GNU image manipulation program</a> to
construct a suitable reference image:</p>

<video controls="" autoplay="true" loop="true" class="screenshot">
  <source src="https://stb-tester.com/blog/images/transparency/create-transparent-template.m4v" />
</video>

<p>Steps:</p>

<ol>
  <li>Open the screenshot.</li>
  <li>Use the crop tool to crop to the area you want to match.</li>
  <li>Right-click on the layer in the “Layers” pane and click “Add Alpha Channel”.</li>
  <li>Use the selection tool to select the area that you want to be transparent.</li>
  <li>Press the “delete” key on your keyboard.</li>
  <li>Use “File &gt; Export” to save the image as a PNG to your test-pack.</li>
</ol>

<p>Here’s the reference image we saved:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/transparency/keyboard-key.png" />
  <figcaption>
    Reference image with transparent centre
  </figcaption>
</figure>

<p>The middle of the key may look like a white box in your web browser – but it
is in fact transparent.</p>

<p>We can now use this image in our test scripts directly:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">matched</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">"keyboard-key.png"</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">matched</span>
<span class="k">print</span> <span class="s">"Selected key %s in region"</span> <span class="o">%</span> <span class="p">(</span>
    <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">matched</span><span class="p">.</span><span class="n">region</span><span class="p">),</span> <span class="n">matched</span><span class="p">.</span><span class="n">region</span><span class="p">)</span></code></pre>
</figure>

<p>More realistically we’d use it as part of a Page Object to find the selected
key:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">KeyboardKey</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">is_visible</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="nf">bool</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">region</span><span class="p">)</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">region</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">m</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">"keyboard-key.png"</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">m</span> <span class="ow">and</span> <span class="n">m</span><span class="p">.</span><span class="n">region</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">label</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">region</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">)</span></code></pre>
</figure>

<p>You can use the <code class="language-plaintext highlighter-rouge">KeyboardKey</code> Page Object like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="n">key</span> <span class="o">=</span> <span class="nc">KeyboardKey</span><span class="p">()</span>
<span class="k">print</span> <span class="s">"Selected letter %s in region %s"</span> <span class="o">%</span> <span class="p">(</span><span class="n">key</span><span class="p">.</span><span class="n">label</span><span class="p">,</span> <span class="n">key</span><span class="p">.</span><span class="n">region</span><span class="p">)</span></code></pre>
</figure>

<h3 id="finding-outline-selections-on-carousels">Finding outline selections on carousels</h3>

<p>On the Roku’s Home page, the focused item is shown by a white outline:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/transparency/roku.png" class="screenshot" />
  <figcaption>
    Screenshot showing Roku "Home" carousel
  </figcaption>
</figure>

<p>How to find which item is currently focused? Create a reference image as
before:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/transparency/roku-outline.png" />
  <figcaption>
    Reference image: Grey selection with transparent contents
  </figcaption>
</figure>

<p>This will match the currently selected tile, independent of what the tile
contains.</p>

<p>We can now use this in our Page Objects:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">CarouselSelection</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="n">ORIGIN</span> <span class="o">=</span> <span class="p">(</span><span class="mi">117</span><span class="p">,</span> <span class="mi">149</span><span class="p">)</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">is_visible</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="nf">bool</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">region</span><span class="p">)</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">region</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">m</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">"roku-outline.png"</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">m</span> <span class="ow">and</span> <span class="n">m</span><span class="p">.</span><span class="n">region</span></code></pre>
</figure>

<h3 id="determining-which-page-were-on-and-checking-background-details">Determining which page we’re on and checking background details</h3>

<p>A current fashion in set-top box UIs is the “clean look” – UIs showing as little
as possible so as to not distract from the content.  Here’s an example:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/transparency/entertainment-populated.jpg" class="screenshot" />
  <figcaption>
    Screenshot of "Entertainment" grid from set-top box.
  </figcaption>
</figure>

<p>Most of the pixels here are going to change depending on what content is
available and what is currently selected. That doesn’t mean there’s nothing to
match on. Using transparency we can invert our idea of matching: instead of
looking for a part of the frame that is unique to this page, we start with an
example frame and delete the areas that contain dynamic content.</p>

<p>This can be useful for:</p>

<ul>
  <li>Identifying what page you’re currently viewing – typically as a part of the
implementation of the <code class="language-plaintext highlighter-rouge">is_visible</code> property on <code class="language-plaintext highlighter-rouge">FrameObject</code> classes.</li>
  <li>Checking for regressions/changes in UI layout.</li>
</ul>

<p>We end up with a template like this:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/transparency/entertainment-grid.png" class="screenshot" />
  <figcaption>
    Transparent template <code>entertainment-grid.png</code>
  </figcaption>
</figure>

<p>This captures the blue background and the light-blue box surrounding the text
on the right hand side.  There are very few features in this image that we’re
matching against.  This template will only match against frames that are
similarly featureless in these areas.  In practice this may be enough to
identify this page, but it can also be used as a “first pass” to discard
non-matches before doing more expensive checks involving OCR.</p>

<p>This type of matching is very fast because the template is the same size as the
frame so we don’t need to search all the locations in the frame to find a
match.</p>

<h3 id="example">Example</h3>

<p>Here in our <code class="language-plaintext highlighter-rouge">is_visible</code> implementation we use the transparent template to check
the page before calling OCR to read the title.</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">EntertainmentGrid</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="n">FrameObject</span><span class="p">):</span>
    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">is_visible</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="nf">return </span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">"entertainment-grid.png"</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">)</span> <span class="ow">and</span>
                <span class="n">self</span><span class="p">.</span><span class="n">title</span> <span class="o">==</span> <span class="s">"Entertainment"</span><span class="p">)</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">title</span><span class="p">():</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="mi">90</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">193</span><span class="p">,</span> <span class="mi">38</span><span class="p">),</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">_frame</span><span class="p">)</span></code></pre>
</figure>

<p>This is great for test-script maintenance. Full page transparent templates are
easy to view and to understand, and when your UI changes they’re easy to update
too.</p>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester v30 released: Transparency support, Ubuntu 18.04 and more!
</title>
        <pubDate>Mon, 25 Feb 2019 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2019/02/25/stb-tester-v30-released</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2019/02/25/stb-tester-v30-released</guid>
        <description>
          &lt;p&gt;Version 30 of the stb-tester core Python API is available. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stbt.match&lt;/code&gt; now
supports transparency: Transparent pixels in the reference image will be
ignored when looking for a match within the video-frame. This is a huge
usability improvement when looking for buttons, selections/highlights, and
similar outlines that have dynamic content or non-rectangular shapes.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Version 30 of the stb-tester core Python API is available. <code class="language-plaintext highlighter-rouge">stbt.match</code> now
supports transparency: Transparent pixels in the reference image will be
ignored when looking for a match within the video-frame. This is a huge
usability improvement when looking for buttons, selections/highlights, and
similar outlines that have dynamic content or non-rectangular shapes.</p>

<p>The run-time environment of the test scripts has also been upgraded from Ubuntu
14.04 to Ubuntu 18.04. This means that any third-party packages that you
install with <em>apt install</em> will be upgraded too.</p>

<p>In particular, Tesseract (the engine used by <code class="language-plaintext highlighter-rouge">stbt.ocr</code>) has been upgraded to
version 4.0. Overall the OCR accuracy should improve slightly, but there may be
regressions or differences in behaviour in some cases.</p>

<p>As always, changes to the <code class="language-plaintext highlighter-rouge">stbt</code> core Python API and the Ubuntu run-time
environment are version-controlled. You can specify the version you want to use
in your <a href="https://stb-tester.com/manual/advanced-configuration#test-pack-stbt-version">stbt.conf</a> file. This allows you to upgrade in a controlled manner,
and to test the upgrade on a branch first.</p>

<p>This release also contains many other improvements and bug-fixes. For full
details see the <a href="https://stb-tester.com/manual/release-notes">v30 release notes</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Testing Alexa integration</title>
        <pubDate>Mon, 22 Oct 2018 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2018/10/22/alexa</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/10/22/alexa</guid>
        <description>
          &lt;p&gt;An end-to-end test for your Alexa integration is easy to automate with
Stb-tester. In this short video we issue commands to Alexa by playing
pre-recorded audio files (“Alexa, play Mr. Robot”). Then we validate that the
command was successful by looking at the video output from the set-top box (in
this video, a Fire TV Stick).&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>An end-to-end test for your Alexa integration is easy to automate with
Stb-tester. In this short video we issue commands to Alexa by playing
pre-recorded audio files (“Alexa, play Mr. Robot”). Then we validate that the
command was successful by looking at the video output from the set-top box (in
this video, a Fire TV Stick).</p>

<p>For example:</p>

<table class="table table-striped">
  <thead>
    <tr>
      <th>Command</th>
      <th>Validation</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alexa, play Mr. Robot</td>
      <td>Wait for the player to launch; read the episode title from the player’s control overlay and check that it says “Mr. Robot”.</td>
    </tr>
    <tr>
      <td>Alexa, next episode</td>
      <td>Read the episode number from the overlay, and check that it is one greater than the previous episode.</td>
    </tr>
    <tr>
      <td>Alexa, jump forward 20 minutes</td>
      <td>Check the playhead position.</td>
    </tr>
  </tbody>
</table>

<iframe src="https://player.vimeo.com/video/296439803?color=f77f00" width="640" height="360" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester support for PyCharm</title>
        <pubDate>Thu, 11 Oct 2018 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2018/10/11/pycharm</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/10/11/pycharm</guid>
        <description>
          &lt;p&gt;Stb-tester now provides &lt;a href=&quot;https://www.jetbrains.com/pycharm/&quot;&gt;PyCharm&lt;/a&gt; integration out of the box, allowing you to
run tests against the real set-top-box, right from your IDE:&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester now provides <a href="https://www.jetbrains.com/pycharm/">PyCharm</a> integration out of the box, allowing you to
run tests against the real set-top-box, right from your IDE:</p>

<figure>
  <img src="https://stb-tester.com/blog/images/pycharm/run-test.png" class="border" />
  <figcaption>
    Run a test from PyCharm IDE by clicking on
    <img src="https://stb-tester.com/blog/images/pycharm/play-icon.png" title="Play icon" />
    next to the test name.
  </figcaption>
</figure>

<p>For instructions see <a href="https://stb-tester.com/manual/ide-configuration">IDE Configuration</a> in the Stb-tester manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Testing the Roku Streaming Stick+ with Stb-tester
</title>
        <pubDate>Mon, 20 Aug 2018 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2018/08/20/roku-streaming-stick</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/08/20/roku-streaming-stick</guid>
        <description>
          &lt;p&gt;Stb-tester has always supported Roku boxes (Roku HD, 2, 3, 4, Express,
Express+, Premiere, Premiere+, and Ultra). Now, Stb-tester also supports the
Roku Streaming Stick and Streaming Stick+.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester has always supported Roku boxes (Roku HD, 2, 3, 4, Express,
Express+, Premiere, Premiere+, and Ultra). Now, Stb-tester also supports the
Roku Streaming Stick and Streaming Stick+.</p>

<figure class="pull-right">
  <img src="https://stb-tester.com/blog/images/roku-streaming-stick/control-feedback-ir.png" />
</figure>

<p>To test your app you need a mechanism to <strong>control</strong> the Roku and a way to get
<strong>feedback</strong> to check that your app is behaving as it should. With the other
Roku models Stb-tester emulates an infrared remote control, but the Roku
Streaming Sticks don’t have an infrared receiver (instead, their remote
controls use <a href="https://en.wikipedia.org/wiki/Wi-Fi_Direct">Wi-Fi Direct</a>).</p>

<p>All Roku models also provide an HTTP-based API for controlling the Roku device,
which is used by Roku’s mobile app. Stb-tester now supports this API natively,
allowing you to test Roku models that don’t have an infrared receiver (that is,
you can now test the Roku Streaming Stick and Streaming Stick+).</p>

<h3 id="configuration">Configuration</h3>

<p>See <a href="https://stb-tester.com/manual/roku">Roku HTTP remote control</a> in the Stb-tester manual for configuration &amp;
usage instructions.</p>

<h3 id="reliability">Reliability</h3>

<p>We have tested 60,000 keypresses over 3 days without a single missed keypress
(we validate that each and every keypress had the expected effect in the Roku’s
UI).</p>

<p>Roku devices never really go to sleep, so you won’t have any issues in waking
the device using the HTTP control mechanism.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester v29 released: Audio support and more!
</title>
        <pubDate>Fri, 22 Jun 2018 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2018/06/22/stb-tester-v29-released</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/06/22/stb-tester-v29-released</guid>
        <description>
          &lt;p&gt;Version 29 of the stb-tester core Python API is now available. This is a
significant release with major new APIs for testing audio.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Version 29 of the stb-tester core Python API is now available. This is a
significant release with major new APIs for testing audio.</p>

<p>You can now test the following scenarios related to <strong>audio</strong>:</p>

<ul>
  <li>Is audio playing at all?</li>
  <li>How long does it take for the audio content to start playing?</li>
  <li>Does the mute button work?</li>
  <li>Do the volume buttons work?</li>
  <li>Do you hear clicks/bloops when you press the keys to navigate around your UI?</li>
  <li>Is the loudness of advertisements within the permitted levels defined by EBU
R128 / ITU-R BS.1770?</li>
  <li>Measure A/V synchronisation (detailed example coming soon).</li>
</ul>

<p>Furthermore, the video-recording of each test-run now includes the audio track.</p>

<p>We have also added APIs for <strong>pressing-and-holding a button</strong> on the remote
control. For example, you can now test these scenarios:</p>

<ul>
  <li>Powering off set-top boxes that require you to hold the power button for
several seconds.</li>
  <li>How fast does the volume bar change as you hold “Volume Up”?</li>
  <li>How soon does the <em>actual</em> volume change as you hold “Volume Up”?</li>
  <li>Does channel zapping continue to occur while you hold the “Channel Down”
button, and does it stop as soon as you release the button?</li>
  <li>Quickly scroll to the end of a carousel or menu.</li>
</ul>

<p>Another new API is <a href="https://stb-tester.com/manual/python-api#stbt.press_and_wait">stbt.press_and_wait</a>, which makes it really easy to
<strong>detect and measure transitions</strong>: For example, has the selection finished
moving after you press up/down/left/right; or transitions to a new screen, such
as going from live TV to the programme guide. This helps with scenarios such as
these:</p>

<ul>
  <li>Frame-accurate performance measurement of animations, for example a menu
selection that slides from one entry to the next. Measure the time for the
animation to start after the keypress, and then time for the animation to
complete.</li>
  <li>Measure how long your Guide takes to appear (time to first frame) and then
the time to populate fully.</li>
  <li>Measure the performance of every single keypress when navigating through your
UI.</li>
  <li>Faster, more robust navigation.</li>
</ul>

<p>This release also contains <strong>many other improvements and bug-fixes</strong>. For full
details see the <a href="https://stb-tester.com/manual/release-notes"><strong>v29 release notes</strong></a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Test-script development workflow</title>
        <pubDate>Tue, 10 Apr 2018 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2018/04/10/development-workflow</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/04/10/development-workflow</guid>
        <description>
          &lt;p&gt;When you are developing new test scripts (or modifying existing ones) we
recommend this workflow:&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>When you are developing new test scripts (or modifying existing ones) we
recommend this workflow:</p>

<figure class="pull-right">
  <img src="https://stb-tester.com/blog/images/workflow/branch-selector.png" />
  <figcaption>Branch selector in Stb-tester portal</figcaption>
</figure>

<ol>
  <li>Make your changes on a branch</li>
  <li>Test your branch</li>
  <li>Make a pull request</li>
  <li>Merge to master</li>
</ol>

<p>Stb-tester helps you to follow best practices by making it easy to follow this
workflow. It stops developers from stepping on each other’s toes, it fosters
communication and peer review, and it ensures that all your changes are tracked
and version-controlled.</p>

<p>Stb-tester will even run a <strong>static analysis</strong> of the code in your pull
requests, automatically. This can catch common issues like syntax errors, some
logic errors, and failure to comply with your team’s coding style.</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/workflow/lint-result.png" class="text-center" /></p>
</figure>

<p>For a detailed explanation see <strong><a href="https://stb-tester.com/manual/workflow">Development Workflow</a></strong> in the Stb-tester
manual.</p>

<p>If your development team has an established workflow feel free to use that
instead — but keep in mind that test-script development can benefit from a
lighter-weight workflow than the workflows that are appropriate in other
contexts.</p>

<p><strong>Bonus tip:</strong> Debugging your test scripts can get tedious if you have to make
a git commit for every change you want to test. <strong>stbt-rig</strong> is a command-line
tool that can run your local code-changes on an Stb-tester node without having
to make git commits or clicking in the web portal. Follow the link above for
instructions.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Testing the Amazon Fire TV with Stb-tester
</title>
        <pubDate>Mon, 26 Mar 2018 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2018/03/26/fire-tv</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/03/26/fire-tv</guid>
        <description>
          &lt;p&gt;Stb-tester supports automated testing on the Amazon Fire TV and Fire TV Stick.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester supports automated testing on the Amazon Fire TV and Fire TV Stick.</p>

<figure>
  <img src="https://stb-tester.com/blog/images/fire-tv/stb-tester-portal-screenshot.png" class="border" />
  <figcaption>
    Stb-tester portal showing live video from an Amazon Fire TV stick
  </figcaption>
</figure>

<figure class="pull-right">
  <img src="https://stb-tester.com/blog/images/fire-tv/control-feedback-cec.png" />
</figure>

<p>To test your app you need a mechanism to <strong>control</strong> the Fire TV and a way to
get <strong>feedback</strong> to check that your app is behaving as it should.</p>

<p>Stb-tester sends CEC commands (over the HDMI connection) to control the Fire
TV, and captures the video over the same HDMI connection to verify your app’s
behaviour. <a href="https://en.wikipedia.org/wiki/Consumer_Electronics_Control">CEC</a> is the same technology that allows you to use your TV’s remote
to control the Amazon Fire.</p>

<h3 id="setup">Setup</h3>

<p>You will need our USB CEC adapter. If we didn’t ship one with your Stb-tester
device, contact <a href="mailto:support@stb-tester.com">support@stb-tester.com</a>. Plug it in as per the diagram below
— the Stb-tester Node will automatically use the CEC adapter if there is no
infrared transmitter connected.</p>

<p>CEC is enabled by default on the Fire TV. If you had previously disabled it,
you can re-enable it by going to Settings &gt; Display &amp; Sounds &gt; HDMI CEC Device
Control.</p>

<figure class="text-center">
  <p><img src="https://stb-tester.com/blog/images/fire-tv/wiring.png" class="text-center" /></p>
</figure>

<h3 id="key-names">Key names</h3>

<p>In your test scripts you can use the following key names:</p>

<figure class="pull-right">
  <img src="https://stb-tester.com/blog/images/fire-tv/remote-control.png" />
</figure>

<ul>
  <li><code class="language-plaintext highlighter-rouge">"KEY_UP"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_DOWN"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_LEFT"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_RIGHT"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_OK"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_BACK"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_HOME"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_OPTIONS"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_REWIND"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_PLAY"</code></li>
  <li><code class="language-plaintext highlighter-rouge">"KEY_FASTFORWARD"</code></li>
</ul>

<h3 id="waking-from-sleep">Waking from sleep</h3>

<p>The Fire TV doesn’t implement the CEC command for “power on”. To work around
this, you can stop the Fire TV from going to sleep by running the following ADB
commands (this setting isn’t available from the Fire TV’s on-screen settings
menus):</p>

<ol>
  <li>First enable ADB in Settings &gt; Device &gt; Developer Options &gt; ADB debugging.</li>
  <li>Go to Settings &gt; Device &gt; About &gt; Network. Note the Fire’s IP address.</li>
  <li>In your terminal, run <code class="language-plaintext highlighter-rouge">adb connect 192.168.1.208</code> (substituting your Fire’s
actual IP address).</li>
  <li>Again in your terminal, run <code class="language-plaintext highlighter-rouge">adb shell settings put secure sleep_timeout 0</code></li>
</ol>

<p>This setting persists after power-cycling the Fire TV.</p>

<p>For more help on steps 1-3 see Amazon’s developer documentation
<a href="https://developer.amazon.com/docs/fire-app-builder/connecting-adb-to-fire-tv.html">Connect to Fire TV Through ADB</a>.</p>

<h3 id="inter-press-delay">Inter-press delay</h3>

<p>If you send several keypresses too quickly, the Fire TV doesn’t always send the
ACK (acknowledgement) specified by the CEC protocol. When this happens,
Stb-tester’s <a href="https://stb-tester.com/manual/python-api#stbt.press">stbt.press</a> function will raise an exception, to avoid false
positives in your tests.</p>

<p>To prevent this from happening you can enforce a minimum delay of 3 seconds
between keypresses, by specifying <code class="language-plaintext highlighter-rouge">interpress_delay_secs</code> in your test-pack’s
<a href="https://stb-tester.com/manual/advanced-configuration#stbt-conf">.stbt.conf</a> file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[press]
interpress_delay_secs = 3
</code></pre></div></div>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Announcing Stb-tester integration with Atlassian Bamboo CI Server
</title>
        <pubDate>Fri, 23 Mar 2018 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2018/03/23/bamboo-ci</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/03/23/bamboo-ci</guid>
        <description>
          The Stb-tester Platform now supports Atlassian&apos;s Bamboo Server for Continuous
Integration (CI). Many of our customers already use Bamboo for continuous
integration, deployment, and delivery. Now, it will only take a few minutes
to get your Stb-tester end-to-end tests running from your Bamboo jobs.

        </description>
        <content:encoded><![CDATA[
          <figure class="pull-right">
  <p><img src="https://stb-tester.com/assets/images/integrations/bamboo-logo.png" alt="" /></p>
</figure>

<p>The <strong>Stb-tester Platform</strong> now supports Atlassian’s <strong><a href="https://www.atlassian.com/software/bamboo">Bamboo</a></strong> Server for
Continuous Integration (CI).</p>

<p>Many of our customers already use Bamboo for continuous integration,
deployment, and delivery. Now, it will only take a few minutes to get your
Stb-tester end-to-end tests running from your Bamboo jobs.</p>

<p>For full instructions see <strong><a href="https://stb-tester.com/manual/continuous-integration">Continuous Integration</a></strong> in the Stb-tester manual.</p>

<p>This is in addition to Stb-tester’s existing first-class support for <a href="https://jenkins.io/">Jenkins</a>.
And integrating with other systems is also possible using our <a href="https://stb-tester.com/manual/rest-api-v2">HTTP REST API</a>
directly.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Cropping reference images in your browser
</title>
        <pubDate>Tue, 20 Mar 2018 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2018/03/20/cropping-reference-images</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/03/20/cropping-reference-images</guid>
        <description>
          &lt;p&gt;When you are capturing reference images for your test scripts, the Stb-tester
Portal now allows you to crop the images directly in your browser.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>When you are capturing reference images for your test scripts, the Stb-tester
Portal now allows you to crop the images directly in your browser.</p>

<p>Start by pressing the <em>Screenshot</em> button underneath the live video view in the
Stb-tester portal (as usual). This will take a lossless screenshot from the
system-under-test as a PNG file.</p>

<p>From here you can drag on the image to crop it: Select the part that you want
to match in your test-case, then press the <em>Download</em> button to save it to your
test-pack.</p>

<figure>
  <p><img src="https://stb-tester.com/blog/images/screenshot-view.png" alt="" /></p>
</figure>

<p>For more details see <a href="https://stb-tester.com/manual/getting-started#reference-images">Reference images</a> in the Stb-tester manual.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester v28 released: Multi-threading support, better OCR, better video recordings
</title>
        <pubDate>Tue, 06 Feb 2018 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2018/02/06/stb-tester-v28-released</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/02/06/stb-tester-v28-released</guid>
        <description>
          &lt;p&gt;Version 28 of the stb-tester core Python API is now available.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Version 28 of the stb-tester core Python API is now available.</p>

<p>Changes to stb-tester’s core Python API are version-controlled. You can specify
the version you want to use in your <code class="language-plaintext highlighter-rouge">.stbt.conf</code> file. See
<a href="https://stb-tester.com/manual/advanced-configuration#test-pack-stbt-version">Advanced Configuration: test_pack.stbt_version</a>
in our manual.</p>

<p>We generally expect that upgrading to new versions will be safe; we strive to
maintain backwards compatibility. But there may be some edge cases that affect
some users, so this mechanism allows you to upgrade in a controlled manner, and
to test the upgrade on a branch first.</p>

<p>Version 28 includes support for multi-threading in your test scripts; better
OCR performance for text on translucent overlays; higher-quality and
higher-framerate video recordings of each test-run; and many API improvements.
For details see the <a href="https://stb-tester.com/manual/release-notes">v28 release notes here</a>.</p>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Announcing the Stb-tester Cloud Platform</title>
        <pubDate>Wed, 17 Jan 2018 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2018/01/17/announcing-the-stb-tester-cloud-platform</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2018/01/17/announcing-the-stb-tester-cloud-platform</guid>
        <description>
          &lt;p&gt;Stb-tester’s cloud-based portal centralizes your test results and dashboards.
It allows remote testing and live monitoring from anywhere in the world —
your devices can be spread across multiple network locations, and your test
team can be remote too.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester’s cloud-based portal centralizes your test results and dashboards.
It allows remote testing and live monitoring from anywhere in the world —
your devices can be spread across multiple network locations, and your test
team can be remote too.</p>

<p>The Stb-tester cloud platform is the result of a year’s work on a new
client/server architecture and new node hardware, based on customer
requirements and feedback. It enables new use-cases such as remote testing and
monitoring, or testing real consumer network conditions from a variety of
locations.</p>

<figure class="pull-right">
  <img src="https://stb-tester.com/blog/images/platform/stb-tester-hdmi-node.jpg" />
  <figcaption>Stb-tester HDMI Node</figcaption>
</figure>

<p>The Stb-tester platform consists of:</p>

<ul>
  <li>A cloud-based <strong>portal</strong>, which provides the user interface and stores your
test results; and</li>
  <li>One or more Stb-tester <strong>nodes</strong> — physical hardware that sits next to your
“devices under test” (DUTs). An Stb-tester node captures video from your DUT,
and executes your automated test scripts.
    <ul>
      <li>The <a href="https://stb-tester.com/solutions">Stb-tester HDMI Node</a> captures video via HDMI for testing a Set-Top
Box, Roku, Apple TV, PlayStation 4, XBox One, or similar devices.</li>
    </ul>
  </li>
</ul>

<p>The Stb-tester cloud platform has seen tremendous adoption since we released it
in April 2017. Our largest customers have upgraded to the new cloud platform
and have expanded their test farms. We have also acquired dozens of new
customers across 3 continents, including large deployments in France and
Belgium.</p>

<p><img src="https://stb-tester.com/blog/images/platform/customers.png" /></p>

<h2 id="designed-to-scale">Designed to scale</h2>

<p>The Stb-tester platform was designed with scalability in mind. Tests are
executed on the Stb-tester nodes, allowing low latency and easy horizontal
scaling to many devices.</p>

<p>The Stb-tester nodes are small and draw around 16W at full load, so you can fit
several nodes, plus your set-top boxes, on a single rack shelf.</p>

<p>The test results from all your devices are aggregated in the portal, where you
can quickly filter to compare results across different devices and software
versions.</p>

<h2 id="simple-fair-pricing">Simple, fair pricing</h2>

<p>Our <a href="https://stb-tester.com/pricing">pricing</a> is a yearly license fee per Stb-tester node (1 node = 1
simultaneous device-under-test). It is cheap to start with a single node, and
you can scale up by adding more nodes. Significant discounts apply for large
test farms.</p>

<p>Compared to our previous product, our pricing has changed to a rolling license
model, which significantly reduces the up-front cost in the first year (total
cost of ownership over 3 years is about the same as our previous pricing
model). The minimum license period is 1 year. There are no cancellation charges
if you choose not to renew.</p>

<p>Customers who prefer an up-front capital expenditure can buy a 5-year license
at a discount. A separate “Enterprise” pricing model is available for test
farms of 100 units or more.</p>

<h2 id="easy-to-set-up-and-manage">Easy to set up and manage</h2>

<p>Just connect your Stb-tester node to the network and to the device-under-test,
and you’re ready to go. From any web browser you can see live video from the
device, control it manually, and run automated tests.</p>

<figure id="portal-screenshot" class="pull-left">
  <img src="https://stb-tester.com/blog/images/platform/portal.png" />
</figure>

<p>The Stb-tester node only needs outbound access to the Stb-tester portal. You
don’t need to open holes in your firewall.</p>

<p>All the configuration of the test-farm is centralized. The Stb-tester nodes are
stateless, so you can swap hardware in or out easily. Furthermore, we manage
the storage, backups, monitoring, and software updates automatically.</p>

<p>Our hardware is rock solid — you can run soak tests for days or weeks without
having to worry about the stability of your video-capture hardware, drivers, or
anything else.</p>

<p>All this makes the Stb-tester platform ideal for teams to “self serve” without
needing a dedicated sysadmin or test infrastructure team.</p>

<h2 id="secure">Secure</h2>

<p>Our platform was built with security as a core feature. Your portal and
test-results database are hosted in a private AWS instance per customer —
they are not multi-tenant. Test artifacts (logs, screenshots, and videos) are
stored securely in S3. Traffic is secured with industry standard HTTPS
encryption, authenticated with client certificates (the nodes) or OAuth (the
human users). All test artifacts are encrypted at rest.</p>

<p>Access control is enforced per user, with the ability to grant read, write, or
admin access. Currently we support GitHub’s single sign-on for user account
management.</p>

<p>The Stb-tester cloud platform is trusted by large operators and OEMs, including
Dish, Telstra, Walmart, and Sony.</p>

<h2 id="modern-tooling">Modern tooling</h2>

<p>The Stb-tester platform integrates with the development tools you are already
using: GitHub for source control, pull requests and code review; Jenkins and
Bamboo for Continuous Integration; IDEs such as PyCharm and Visual Studio Code
for test-development. We also provide a <a href="https://stb-tester.com/manual/rest-api-v2">REST API</a> to enable you to develop
custom integrations.</p>

<p>Additional integrations will be provided out-of-the-box based on customer
demand.</p>

<figure>
  <img src="https://stb-tester.com/blog/images/platform/integrations.png" />
</figure>

<h2 id="backwards-compatible">Backwards compatible</h2>

<p>The new hardware is 100% compatible with test scripts written for our previous
product. Backwards compatibility is one of our core company values.</p>

<h2 id="request-a-demo">Request a demo!</h2>

<p>To request a demo, please contact <a href="mailto:sales@stb-tester.com">sales@stb-tester.com</a>.</p>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Testing video playback on mobile devices
</title>
        <pubDate>Tue, 21 Feb 2017 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2017/02/21/testing-video-playback-on-mobile-devices</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2017/02/21/testing-video-playback-on-mobile-devices</guid>
        <description>
          &lt;p&gt;There are several mature open source and proprietary tools for testing the user
interface of mobile apps on Android and iOS devices, but there aren’t many
solutions for internet service providers or video-on-demand streaming services
who want to test &lt;strong&gt;video playback&lt;/strong&gt; on real mobile devices to validate their
end-to-end video delivery. In this article I’ll discuss various ways of getting
video frames from a mobile device for automated testing, and the advantages and
trade-offs of each approach.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>There are several mature open source and proprietary tools for testing the user
interface of mobile apps on Android and iOS devices, but there aren’t many
solutions for internet service providers or video-on-demand streaming services
who want to test <strong>video playback</strong> on real mobile devices to validate their
end-to-end video delivery. In this article I’ll discuss various ways of getting
video frames from a mobile device for automated testing, and the advantages and
trade-offs of each approach.</p>

<figure id="android-hierarchy-view">
  <img src="https://stb-tester.com/blog/images/mobile-video/android-hierarchy-view.png" />
  <figcaption>
    UI hierarchy view of the "Now TV" Android app,<br />
    corresponding to the screenshot above.
  </figcaption>
</figure>

<p>Automated user-interface testing tools for mobile apps tend to test an internal
representation of the UI — a hierarchical structure of nodes such as the
example shown here for the “Now TV” app. (If you’re familiar with web
development, this is similar to a web page’s <a href="https://en.wikipedia.org/wiki/Document_Object_Model">DOM</a>.) Tools like <a href="http://appium.io/">Appium</a> or
<a href="http://calaba.sh/">Calabash</a> can check that a node with the ID “videoplayer” is present, but they
can’t check whether the video player is actually working.</p>

<h3 id="capturing-screenshots-over-usb">Capturing screenshots over USB</h3>

<p>These tools do have the capability to capture screenshots, which you could
analyse with Stb-tester’s image-processing APIs. Unfortunately, these
screenshots may not include the video plane, depending on the app. For example,
you don’t see video content with Netflix, but you do with Now TV:</p>

<table class="table">
  <thead>
    <tr>
      <th style="text-align: center">App</th>
      <th style="text-align: center">iPhone</th>
      <th style="text-align: center">Android</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">Netflix</td>
      <td style="text-align: center"><a href="https://stb-tester.com/blog/images/mobile-video/usb-iphone-netflix.png"><img src="https://stb-tester.com/blog/images/mobile-video/usb-iphone-netflix.png" alt="" /></a></td>
      <td style="text-align: center"><em>Returns invalid image data</em></td>
    </tr>
    <tr>
      <td style="text-align: center">Now TV</td>
      <td style="text-align: center"><a href="https://stb-tester.com/blog/images/mobile-video/usb-iphone-nowtv.png"><img src="https://stb-tester.com/blog/images/mobile-video/usb-iphone-nowtv.png" alt="" /></a></td>
      <td style="text-align: center"><a href="https://stb-tester.com/blog/images/mobile-video/usb-android-nowtv.png"><img src="https://stb-tester.com/blog/images/mobile-video/usb-android-nowtv.png" alt="" /></a></td>
    </tr>
  </tbody>
</table>

<p>Presumably the difference is due to different DRM technologies used by the two
apps.</p>

<p>Note that the white text in the Netflix screenshot is a subtitle, which is
layered on top of the video by the Netflix app. Only the video itself is
missing from the screenshots.</p>

<p>The frame rate (of frames delivered to the test script) is poor, because the
mobile device is encoding and transferring large PNGs. Expect around 2 to 0.2
frames per second depending on the device and the complexity of the image in a
particular frame. This might increase the load on the device-under-test, which
could affect subtle timing conditions in your app — if your test is trying to
reproduce some kind of intermittent defect, its results might not be
representative.</p>

<p>The great advantage of this method is that it is simple, it doesn’t require any
test hardware, and it is provided by many mobile testing tools. Stb-tester
supports this method (we use <a href="https://developer.android.com/studio/command-line/adb.html">ADB</a> to capture screenshots from Android devices,
and <a href="https://github.com/facebook/WebDriverAgent">WebDriverAgent</a> for iOS).</p>

<h3 id="capturing-video-over-hdmi">Capturing video over HDMI</h3>

<p>Apple iPhones &amp; iPads can output video to HDMI using a
<a href="http://www.apple.com/uk/shop/product/MD826ZM/A/lightning-digital-av-adapter">Lightning Digital AV Adapter</a>. Similarly, some (but not all) Android devices
can output HDMI using an <a href="http://www.mhltech.org/">MHL</a> cable. See a full list of supported Android
devices <a href="http://www.mhltech.org/devices.aspx">here</a>.</p>

<p>Once again, it’s up to the app to support this. This time the situation is
reversed: Netflix is happy to play the video over HDMI, whereas Now TV brings
up a dialog saying “Sorry, we don’t support HDMI streaming to your TV from
mobile devices”.</p>

<table class="table">
  <thead>
    <tr>
      <th style="text-align: center">App</th>
      <th style="text-align: center">iPhone</th>
      <th style="text-align: center">Android</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">Netflix</td>
      <td style="text-align: center"><a href="https://stb-tester.com/blog/images/mobile-video/hdmi-iphone-netflix.png"><img src="https://stb-tester.com/blog/images/mobile-video/hdmi-iphone-netflix.png" alt="" /></a></td>
      <td style="text-align: center"><a href="https://stb-tester.com/blog/images/mobile-video/hdmi-android-netflix.png"><img src="https://stb-tester.com/blog/images/mobile-video/hdmi-android-netflix.png" alt="" /></a></td>
    </tr>
    <tr>
      <td style="text-align: center">Now TV</td>
      <td style="text-align: center"><a href="https://stb-tester.com/blog/images/mobile-video/hdmi-iphone-nowtv.png"><img src="https://stb-tester.com/blog/images/mobile-video/hdmi-iphone-nowtv.png" alt="" /></a></td>
      <td style="text-align: center"><a href="https://stb-tester.com/blog/images/mobile-video/hdmi-android-nowtv.png"><img src="https://stb-tester.com/blog/images/mobile-video/hdmi-android-nowtv.png" alt="" /></a></td>
    </tr>
  </tbody>
</table>

<p>Note that the Netflix behaviour is slightly different across platforms. On
Android the device’s screen is mirrored to the HDMI output, so UI elements
like the playback controls appear on both screens. On iOS the video plays on
the HDMI output while the device goes into “second screen remote control”
mode: The device’s screen shows a static image plus the playback controls.</p>

<figure class="pull-right">
  <a href="https://stb-tester.com/blog/images/mobile-video/hdmi-android-netflix-portrait.png">
    <img src="https://stb-tester.com/blog/images/mobile-video/hdmi-android-netflix-portrait.png" />
  </a>
  <figcaption>
    HDMI output is letterboxed vertically<br />
    when the phone is in portrait orientation.
  </figcaption>
</figure>

<p>When you’re not playing video, all devices mirror the screen to the HDMI
output. This works in all apps including the operating system home screen. When
the device is in portrait mode you get some letterboxing on the sides (HDMI
doesn’t support portrait resolutions).</p>

<p>A big disadvantage of this method is that it doesn’t leave a USB connection
available, so the PC that is running the tests will have to find another way to
send input events (simulated taps &amp; swipes) to the mobile device. The Apple
adapter and Android MHL cables all have an additional USB connector, but this
is used exclusively to power the mobile device. On Android devices you can work
around this by enabling <a href="https://stackoverflow.com/questions/2604727/how-can-i-connect-to-android-with-adb-over-tcp">ADB over TCP/IP</a>, but this requires a USB connection
to set up in the first place so it will require manual intervention if the
device reboots or loses WiFi connection.</p>

<h3 id="capturing-video-with-a-camera">Capturing video with a camera</h3>

<p>This is the most “black box” approach of all, so it works with any device and
any app. Just point a camera at the device’s screen!</p>

<p>Our product, the <a href="https://stb-tester.com/videos/the-stb-tester-camera-for-tvs-and-mobile-devices">Stb-tester CAMERA</a>, performs automatic calibration on each
video frame in real time so your test script sees only the mobile device’s
screen, regardless of the camera’s position.</p>

<iframe src="https://player.vimeo.com/video/204961185?autoplay=1&amp;loop=1&amp;color=f77f00&amp;title=0&amp;byline=0&amp;portrait=0" width="640" height="360" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>

<p>We have made the <a href="https://stb-tester.com/videos/the-stb-tester-camera-for-tvs-and-mobile-devices">Stb-tester CAMERA</a> extremely straight-forward to install and
configure. Nevertheless, it does have some disadvantages: There is more
hardware and it takes up more space. You need to cover the mobile device and
camera with a box to eliminate reflections on the screen. The analogue nature
of the process introduces some lossiness, and in particular some motion blur.</p>

<h3 id="summary-of-video-capture-methods">Summary of video-capture methods</h3>

<p>The following table summarises the pros &amp; cons of the various methods for
capturing video frames from iOS &amp; Android devices:</p>

<table class="table">
  <thead>
    <tr>
      <th> </th>
      <th style="text-align: center">USB</th>
      <th style="text-align: center">HDMI</th>
      <th style="text-align: center">CAMERA</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Works on all devices</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✓</td>
    </tr>
    <tr>
      <td>Works on all apps</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✓</td>
    </tr>
    <tr>
      <td>Allows sending input events over USB</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✓</td>
    </tr>
    <tr>
      <td>Black box</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
    </tr>
    <tr>
      <td>Frame rate</td>
      <td style="text-align: center">Slow</td>
      <td style="text-align: center">Full source frame rate</td>
      <td style="text-align: center">Camera’s frame rate</td>
    </tr>
    <tr>
      <td>Picture quality</td>
      <td style="text-align: center">Lossless</td>
      <td style="text-align: center">Lossless</td>
      <td style="text-align: center">Lossy</td>
    </tr>
    <tr>
      <td>Additional hardware required</td>
      <td style="text-align: center">None</td>
      <td style="text-align: center">HDMI video-capture</td>
      <td style="text-align: center">Camera</td>
    </tr>
  </tbody>
</table>

<h3 id="our-recommendations">Our recommendations</h3>

<p>If you don’t need to verify video playback and you just want to test user
interaction within an app, we recommend a tool like Appium, which is tailored
specifically for mobile UI testing. Services like <a href="https://saucelabs.com/">Sauce Labs</a> and <a href="http://bitbar.com/testing/">Testdroid</a>
can even host the mobile devices for you, so you don’t have to deal with dozens
of physical devices.</p>

<p>If you’re already using Stb-tester to test a set-top box (using HDMI
video-capture) and you want to test the interaction between the set-top box and
a “second screen” mobile app (remote control, TV guide, etc): Use USB
screenshot capture from your existing Stb-tester scripts.</p>

<p>If you really do need a black-box view of your device’s screen (to verify video
playback on any app &amp; device, especially useful for monitoring teams): Use the
<a href="https://stb-tester.com/videos/the-stb-tester-camera-for-tvs-and-mobile-devices">Stb-tester CAMERA</a>.</p>

<hr />


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Controlling the Sony PlayStation 4 for test automation
</title>
        <pubDate>Thu, 05 Jan 2017 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2017/01/05/ps4-control</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2017/01/05/ps4-control</guid>
        <description>
          &lt;p&gt;To test a PS4 app you need a mechanism to &lt;strong&gt;control&lt;/strong&gt; the PS4 and a way to get
&lt;strong&gt;feedback&lt;/strong&gt; to check that your app is behaving as it should.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>To test a PS4 app you need a mechanism to <strong>control</strong> the PS4 and a way to get
<strong>feedback</strong> to check that your app is behaving as it should.</p>

<figure class="pull-right border">
  <img src="https://stb-tester.com/blog/images/ps4-control/control-feedback-ir.png" />
  <p>Set-Top Box</p>
  <img src="https://stb-tester.com/blog/images/ps4-control/control-feedback-cec.png" />
  <p>PS4</p>
</figure>

<p><strong><a href="https://stb-tester.com">Stb-tester</a></strong> uses HDMI video-capture for feedback, and –traditionally–
infrared to control set-top boxes. But the PS4 doesn’t have an infrared
receiver. Since stb-tester v27 we can now control the PS4 using <strong><a href="https://en.wikipedia.org/wiki/Consumer_Electronics_Control">CEC</a></strong>
commands over the HDMI connection.</p>

<p>We tried several control mechanisms, and CEC was the only one that worked
consistently and reliably. The PS4 allows you to plug in a USB keyboard, so we
considered using a <a href="https://flirc.tv/more/flirc-usb">Flirc</a> Infrared-to-USB-keyboard dongle. However the USB
keyboard input only works with some apps, and it doesn’t work at the PS4 login
screen. Sony provides a mobile app with remote control capabilities, but it has
similar limitations.</p>

<table class="table table-striped">
  <thead>
    <tr>
      <th>Action</th>
      <th style="text-align: center">PS4 controller</th>
      <th style="text-align: center">HDMI CEC</th>
      <th style="text-align: center">USB keyboard</th>
      <th style="text-align: center">Android app</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Power on the PS4</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✗</td>
    </tr>
    <tr>
      <td>Log in</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✗</td>
    </tr>
    <tr>
      <td>Navigate PS4 main UI</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
    </tr>
    <tr>
      <td>Navigate app</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">✗</td>
    </tr>
  </tbody>
</table>

<p>The CEC control mechanism is very reliable: I ran a soak test for 3 days and
there wasn’t a single missed keypress in 120,000 keypresses. The test script
used CEC commands to navigate right &amp; left through a menu, and after every
keypress it checked that the selection had moved to the expected menu item.</p>

<iframe src="https://player.vimeo.com/video/198213646?autoplay=1&amp;loop=1&amp;color=f77f00&amp;title=0&amp;byline=0&amp;portrait=0" width="640" height="360" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>

<h3 id="setup">Setup</h3>

<p>You will need our USB CEC adapter. If we didn’t ship one with your Stb-tester
device, contact <a href="mailto:support@stb-tester.com">support@stb-tester.com</a>. Plug it in as per the diagram below
— the Stb-tester Node will automatically use the CEC adapter if there is no
infrared transmitter connected.</p>

<p>To enable CEC on the PS4, go to Settings &gt; System &gt; <a href="http://manuals.playstation.net/document/gb/ps4/settings/devicelink.html">Enable HDMI Device Link</a>.
On the PS3 it’s System Settings &gt; Control for HDMI.</p>

<p><img src="https://stb-tester.com/blog/images/ps4-control/wiring.png" /></p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Build your own serial-to-ethernet server with a Raspberry Pi
</title>
        <pubDate>Fri, 28 Oct 2016 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2016/10/28/build-your-own-serial-to-ethernet-server</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2016/10/28/build-your-own-serial-to-ethernet-server</guid>
        <description>
          &lt;p&gt;A Serial Terminal Server makes a serial connection available over the network,
so that your application can access your devices without a direct serial
connection. Typically this is used to establish a bi-directional connection
between networked applications and older industrial equipment. In this article
we will focus on a much simpler use case: Capturing serial logs, where we only
need communication in one direction — a common use case when testing set-top
boxes.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>A Serial Terminal Server makes a serial connection available over the network,
so that your application can access your devices without a direct serial
connection. Typically this is used to establish a bi-directional connection
between networked applications and older industrial equipment. In this article
we will focus on a much simpler use case: Capturing serial logs, where we only
need communication in one direction — a common use case when testing set-top
boxes.</p>

<p>First the complete solution, for the impatient:</p>

<figure class="highlight">
  <pre><code class="language-sh" data-lang="sh"><span class="nv">DEVICE</span><span class="o">=</span>/dev/serial/by-path/platform-3f980000.usb-usb-0:1.3.3.1:1.0-port0
<span class="nv">PORT</span><span class="o">=</span>4953
socat <span class="nt">-b</span> 2048 <span class="nt">-u</span> <span class="s2">"</span><span class="se">\"</span><span class="nv">$DEVICE</span><span class="se">\"</span><span class="s2">,b115200,crnl,raw,ignoreeof,echo=0"</span> - &lt;/dev/null <span class="se">\</span>
    | ts %Y-%m-%dT%H:%M:%S%z <span class="se">\</span>
    | gst-launch-1.0 fdsrc <span class="o">!</span> text/plain <span class="o">!</span> queue <span class="se">\</span>
                     <span class="o">!</span> tcpserversink <span class="nb">sync</span><span class="o">=</span><span class="nb">false </span><span class="nv">host</span><span class="o">=</span>0.0.0.0 <span class="nv">port</span><span class="o">=</span><span class="nv">$PORT</span></code></pre>
</figure>

<p>This uses <a href="http://www.dest-unreach.org/socat/doc/socat.html">socat</a>, the Swiss Army Knife of networking; <a href="https://linux.die.net/man/1/ts">ts</a> from the
<a href="https://joeyh.name/code/moreutils/">moreutils</a> package to timestamp each line; and <a href="https://gstreamer.freedesktop.org/">GStreamer</a> as our TCP server.</p>

<p>The above socat command transfers data from the serial device to standard
output. The two layers of quoting are necessary so that socat doesn’t treat the
“:” in the device path as separators. See the <a href="http://www.dest-unreach.org/socat/doc/socat.html">socat manual</a> for details
on the other parameters.</p>

<p>Then we pipe socat’s output to <code class="language-plaintext highlighter-rouge">ts</code>, which prepends an ISO 8601 timestamp to
each line.</p>

<p>Finally, <code class="language-plaintext highlighter-rouge">gst-launch</code> runs a GStreamer pipeline that reads the text data from
its standard input, starts up a server listening on the specified TCP port, and
sends the data to any clients that connect. GStreamer is perhaps overkill for
this but it gets the job done, and it supports multiple clients
connecting/disconnecting and reading the same data simultaneously.</p>

<p>You’ll want to configure your init system to start the above service
automatically on startup. I’ll leave that as an exercise to the reader.</p>

<h4 id="testing-it">Testing it</h4>

<p>You can test this using netcat. This should print your set-top box’s serial
output to the console:</p>

<figure class="highlight">
  <pre><code class="language-sh" data-lang="sh">nc <span class="nv">$serial_server_address</span> 4953</code></pre>
</figure>

<p>Or using socat:</p>

<figure class="highlight">
  <pre><code class="language-sh" data-lang="sh">socat TCP:<span class="nv">$serial_server_address</span>:4953 -</code></pre>
</figure>

<h4 id="installing-the-dependencies">Installing the dependencies</h4>

<p>Assuming you’re using Raspbian on a Raspberry Pi, here’s how to install these
tools:</p>

<figure class="highlight">
  <pre><code class="language-sh" data-lang="sh">apt-get update <span class="o">&amp;&amp;</span>
apt-get <span class="nb">install</span> <span class="se">\</span>
    gstreamer1.0-plugins-base <span class="se">\</span>
    gstreamer1.0-tools <span class="se">\</span>
    moreutils <span class="se">\</span>
    socat</code></pre>
</figure>

<h4 id="specifying-the-serial-device">Specifying the serial device</h4>

<p>On a single server you can have multiple versions of the above script serving
different serial devices, as long as each one uses a different TCP port.</p>

<p>We use <code class="language-plaintext highlighter-rouge">/dev/serial/by-path/...</code> (instead of <code class="language-plaintext highlighter-rouge">/dev/ttyUSBx</code>) because it won’t
change across reboots. It refers to the physical USB port that your serial
adapter is plugged into.</p>

<p>To find the path of your serial device, just run <code class="language-plaintext highlighter-rouge">ls /dev/serial/by-path</code>
before and after plugging it in.</p>

<h4 id="usb-serial-adapters">USB serial adapters</h4>

<p>For out-of-the-box Linux support and good reliability, you only have two
options:</p>

<ul>
  <li>The <a href="https://www.tripplite.com/keyspan-high-speed-usb-to-serial-adapter~USA19HS/">Tripp-Lite Keyspan</a> adapter.</li>
  <li>Anything using <a href="http://www.ftdichip.com/Products/Cables/USBRS232.htm">FTDI</a> chips (buy direct from FTDI or from reputable vendors,
as there are counterfeit chips on the market).</li>
</ul>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>How we test the stb-tester ONE: Video-capture latency &amp; accuracy
</title>
        <pubDate>Tue, 05 Jul 2016 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2016/07/05/latency-measurements</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2016/07/05/latency-measurements</guid>
        <description>
          &lt;p&gt;&lt;strong&gt;Updated 17 May 2017 with figures for our new hardware’s accuracy.&lt;/strong&gt;&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p><strong>Updated 17 May 2017 with figures for our new hardware’s accuracy.</strong></p>

<p>Some of our users care about frame-accurate performance measurements. For
example, how many frames of video does it take a menu transition to complete?
The <a href="https://stb-tester.com/solutions">Stb-tester HDMI</a> provides the following guarantees:</p>

<ul>
  <li>Your test scripts can process every frame of video, up to 60 frames per
second, using the APIs provided by stb-tester (like <a href="https://stb-tester.com/manual/python-api#stbt.match">match</a> and
<a href="https://stb-tester.com/manual/python-api#stbt.wait_for_motion">wait_for_motion</a>) or with custom image-processing using OpenCV.</li>
  <li>Stb-tester reports the timestamp of each frame.</li>
  <li>The timestamp is in wall-clock time (UTC), so you can compare it against log
entries from the device-under-test, your head-end systems, etc.</li>
  <li><del>On the <a href="https://stb-tester.com/stb-tester-one">Stb-tester ONE</a> the timestamp is accurate to within 35ms. At 60
frames per second, this is 2 frames of video.</del> On our new
<a href="https://stb-tester.com/solutions">Stb-tester HDMI</a> hardware the timestamp is accurate to within 0.9ms (at 60
frames per second a single frame is 16ms, so stb-tester is providing
frame-accurate timings by a wide margin).</li>
  <li>There is no long-term drift (we’ve tested every frame over 4 days of
continuous test execution).</li>
</ul>

<h3 id="how-we-test--validate-video-capture-timestamps">How we test &amp; validate video-capture timestamps</h3>

<p>Our test system consists of an stb-tester ONE capturing the video from a
Raspberry Pi’s HDMI output:</p>

<p><img src="https://stb-tester.com/blog/images/latency/test-setup.png" alt="" /></p>

<p>The Raspberry Pi is generating a video on the fly. Each frame of video has a
time drawn on it in binary — this is the time at which the frame will be sent
over HDMI, according to the Raspberry Pi’s MMAL video subsystem. We have
open-sourced the code that generates and displays this video: See
<a href="https://github.com/stb-tester/latency-clock">stb-tester/latency-clock</a> on github.</p>

<p>The receiver (an stb-tester Python script) decodes the time drawn on the frame,
and compares it against the time reported by the video-capture process.</p>

<p>The Raspberry Pi uses a GPS-stabilised clock, by using a GPS expansion board.
This keeps the Rasperry Pi’s system clock stable to ~20µs (as reported by
<code class="language-plaintext highlighter-rouge">ppstest</code>). The Raspberry Pi also runs an NTP server; the stb-tester ONE runs
an NTP client that synchronises against the Raspberry Pi’s clock, to an
accuracy of ~300µs (as reported by <code class="language-plaintext highlighter-rouge">ntpq</code>). For instructions see
<a href="http://ava.upuaut.net/?p=726">Not quite 5 minute guide to making an NTP Server</a>.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Run your tests!</title>
        <pubDate>Wed, 29 Jun 2016 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2016/06/29/run-your-tests</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2016/06/29/run-your-tests</guid>
        <description>
          &lt;p&gt;If you have already automated one or more testcases, run them! Ideally run them
every time a developer commits a change to the system-under-test. If that isn’t
possible, at least run the tests against nightly builds. Run! Your! Tests!&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>If you have already automated one or more testcases, run them! Ideally run them
every time a developer commits a change to the system-under-test. If that isn’t
possible, at least run the tests against nightly builds. Run! Your! Tests!</p>

<p>It sounds obvious, but so many organisations fail at test automation because
they don’t run the tests often enough to get real value. We have seen teams
spend a year (no exaggeration) building elaborate test infrastructure without
producing any test results. Run your tests! Don’t spend any more time
automating new testcases until you are consistently running the tests you
already have!</p>

<h3 id="costs-of-automated-vs-manual-testing">Costs of automated vs. manual testing</h3>

<p>The costs of automated testing are very different to the costs of manual
testing. A manual testcase is relatively cheap to write and relatively
expensive to run, whereas automated testcases are relatively expensive to write
and very cheap to run — almost free.</p>

<div class="text-center">
  <p><img src="https://stb-tester.com/blog/images/costs-of-manual-and-automated-testing.png" /></p>
</div>

<p>So what’s going to give you more value for the least effort: Writing more
automated tests, or running them more often?</p>

<p>I’m not suggesting that you shouldn’t aim for high test coverage. But pick the
low-hanging fruit first!</p>

<h3 id="run-your-tests-against-more-builds">Run your tests against more builds</h3>

<p>If your QA team gets infrequent releases from your development team, and you
only run your automated tests against each release, some of your tests will
fail because they haven’t kept up with changes to your set-top box’s UI.</p>

<p>If you only run your tests once a month, and you also have to fix the tests
each time, then it might be cheaper to stick with manual testing!</p>

<p>To take advantage of automation’s strengths, you want to be running your tests
against every change made by the development teams, in a Continuous Integration
system (CI). The benefits of CI are well known: Developers can work faster and
more productively, with confidence that they aren’t introducing regressions.
Bugs are caught earlier, and thus they are easier and cheaper to fix.</p>

<h3 id="run-your-tests-on-more-devices">Run your tests on more devices</h3>

<p>If you need to support several different set-top box models, you will often get
more value by extending your existing tests so that they work on all the
different models, than by increasing the test coverage for a single device.</p>

<p>If the user journeys are similar across your devices, you can use the
<a href="https://stb-tester.com/manual/python-api#stbt.FrameObject">Frame Object</a> technique to handle UI variations while using the same test
scripts for all your devices.</p>

<h3 id="run-your-tests-more-times-soak-testing-for-stability">Run your tests more times: Soak testing for stability</h3>

<p>Even the most trivial testcases can sometimes find memory leaks and crashes if
you run them long enough. If your set-top box is going to sit idle, why not
leave your tests running all night? You’ll be surprised at the bugs you’ll
find.</p>

<p>Soak-testing is especially valuable if you’re a set-top box manufacturer or
integrator. If you’re developing an app for someone else’s device you’ll be
less interested in device instabilities, but a soak test can still find
resource leaks in your own app.</p>

<p>Even if you don’t have time to look into every test failure, you can get good
value by implementing some simple health-checks to detect crashes, spontaneous
reboots, and memory leaks.</p>

<p>And even if you don’t look at the results of every overnight soak, run it
anyway! That way you’ll have data if you need to answer “when did this stop
working?” or “when did this start getting slower?”.</p>

<h3 id="run-your-tests-more-times-soak-testing-for-functional-coverage">Run your tests more times: Soak testing for functional coverage</h3>

<p>If you run your tests in a random order you will end up exercising user journeys
that aren’t explicitly coded into your test scripts, and this can often uncover
bugs that you wouldn’t find otherwise. (These bugs tend to be the kind that are
really annoying to diagnose if they’re only noticed in the wild!)</p>

<h3 id="what-do-i-do-next">What do I do next?</h3>

<p>Don’t spend any more time automating new testcases until you are consistently
running the testcases you already have.</p>

<p>Don’t let this be a manual process. Set up a <a href="https://jenkins.io/">Jenkins</a> job (or similar) that
runs every night: It deploys the latest software build to your
device-under-test, and runs all your automated tests in a random order, for 16
hours (or however long it takes before you get into the office the next
morning).</p>

<p>Depending on your development team’s process and tools, deploying the latest
software build to your device-under-test could be the hardest part to automate.
Don’t give up! On previous projects we have automated the update process of
set-top box firmware that requires plugging in a physical USB stick, by using a
<a href="https://blog.danman.eu/usb-switch-for-2-pc-under-linux/">controllable USB switch</a>.</p>

<p>Once this is in place, the next step (automatically testing every change in
Continuous Integration) will be easy!</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>A post-mortem report on the stb-tester ONE&apos;s infrared transmitter reliability
</title>
        <pubDate>Thu, 26 May 2016 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2016/05/26/ir-post-mortem</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2016/05/26/ir-post-mortem</guid>
        <description>
          &lt;p&gt;This is a technical report on our investigation into an intermittent problem
with the stb-tester ONE’s infrared transmitter.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>This is a technical report on our investigation into an intermittent problem
with the stb-tester ONE’s infrared transmitter.</p>

<p>If you’re a customer of ours and you have an stb-tester ONE, we will be posting
you a replacement infrared transmitter free of charge within the next few days.
All you have to do is install the <a href="https://stb-tester.com/manual/release-notes#v24-7">v24.7 software update</a> on your stb-tester
ONE, unplug the old infrared transmitter, and plug in the new one. The new
transmitter is perfectly reliable – we have tested more than 500,000
keypresses without a single missed keypress.</p>

<p>This doesn’t affect our customers with larger test rigs (who are using the
RedRat irNetBox instead of our USB infrared transmitters).</p>

<p>Technical summary: Our old infrared transmitter used the FTDI FT232R
USB-to-serial chip in bit-bang mode. We proved that the FT232R’s clock is
unreliable in bit-bang mode, even the newer “C” revision of the chip. The FTDI
FT230X chip doesn’t have such problems.</p>

<p>Read on for details of the defect, the solution, and our test methodology.</p>

<h3 id="reproducing-the-problem-missed-keypresses">Reproducing the problem: Missed keypresses</h3>

<p>We became aware that the infrared transmitter we ship with the stb-tester ONE
wasn’t entirely reliable: When your test script called <a href="https://stb-tester.com/manual/python-api#stbt.press">stbt.press</a>, sometimes
the device-under-test wouldn’t react – it didn’t see the keypress.</p>

<p>When facing an intermittent defect, the first thing we need is statistically
significant data on the reproducibility of the defect. Otherwise we won’t know
if our changes are helping or hurting, or making any difference at all. So we
picked a Roku box as our target –the Roku is very reliable, for a consumer
electronics device– and we wrote a test script that sends 20 keypresses,
checking that every keypress had the expected effect:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_ir_transmitter</span><span class="p">():</span>
    <span class="nf">to_roku_home</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
        <span class="nf">press</span><span class="p">(</span><span class="s">"KEY_DOWN"</span><span class="p">)</span>
        <span class="k">assert</span> <span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nf">find_selection</span><span class="p">().</span><span class="n">text</span> <span class="o">==</span> <span class="s">"My Feed"</span><span class="p">)</span>
        <span class="nf">press</span><span class="p">(</span><span class="s">"KEY_UP"</span><span class="p">)</span>
        <span class="k">assert</span> <span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nf">find_selection</span><span class="p">().</span><span class="n">text</span> <span class="o">==</span> <span class="s">"Home"</span><span class="p">)</span></code></pre>
</figure>

<p>Starting from the Roku home screen where the menu selection is on “Home”, this
testcase presses DOWN and checks that the menu selection has moved to “My
Feed”. Then it presses UP and checks that the selection has moved back to
“Home”. It repeats this 10 times for a total of 20 keypresses. <em><a href="https://stb-tester.com/manual/python-api#stbt.press">press</a></em> and
<em><a href="https://stb-tester.com/manual/python-api#stbt.wait_until">wait_until</a></em> are functions provided by stb-tester; <em>to_roku_home</em> and
<em>find_selection</em> are Roku-specific helper functions that we have defined
elsewhere in our test-pack. The whole testcase only takes a few seconds; you
can watch a single run of the testcase (where every keypress succeeded) in the
video below:</p>

<iframe src="https://player.vimeo.com/video/167164322" width="640" height="360" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>

<p>After running the testcase repeatedly for a few hours we have 1,368 test-runs,
and 79 of those failed. Each test-run sends 20 keypresses, so we saw 79 missed
keypresses out of approximately 25,800 keypresses: 0.3% of all keypresses were
missed.</p>

<p>In the video below you can watch me spot-check some of the test failures, just
to make sure that the failures are really caused by missed keypresses and not
some other issue.</p>

<iframe src="https://player.vimeo.com/video/167271801" width="640" height="360" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>

<p>Running the same testcase against a different Roku model gave much worse
results: About 4% missed keypresses. So it seems that some infrared receivers
are more susceptible than others. At least we’ve found a good device to test
our future fixes against!</p>

<p><img src="https://stb-tester.com/blog/images/ir-post-mortem/results-from-susceptible-roku.png" alt="" /></p>

<p>Now: Is it really the fault of our infrared transmitter? It could be caused by
our infrared config file (which describes the protocol used by the Roku’s
remote control), or by the Roku itself. To check, we ran the same test using
the <a href="http://www.redrat.co.uk/products/#redrat3">RedRat3</a> infrared transmitter. RedRat is a company based near Cambridge,
UK, who specialise in infrared and other remote control technologies; they are
the gold standard in infrared control automation. The RedRat3 is a USB infrared
transmitter, but unlike ours, it outputs a high-power signal so it isn’t
usually suitable in a test lab with dozens of set-top boxes that are running
independent testcases.</p>

<p>Using the same test script, we didn’t see a single failure in over 30,000
keypresses using the RedRat3. This rules out the Roku and our infrared config
file, leaving only our USB infrared transmitter hardware or software to blame.</p>

<p>We also tested the RedRat <a href="http://www.redrat.co.uk/products/#irnetbox">irNetBox</a> and we didn’t see any missed keypresses
either. So our customers with larger test rigs aren’t affected by this issue,
as they use the RedRat irNetBox instead of our USB infrared transmitters.</p>

<h3 id="analysing-the-output-from-our-infrared-transmitter">Analysing the output from our infrared transmitter</h3>

<p>Infrared signals are a sequence of pulses and spaces. This is the signal for
the Roku’s DOWN button. It’s a long header pulse &amp; space, followed by a
sequence of shorter pulses &amp; spaces:</p>

<p><img src="https://stb-tester.com/blog/images/ir-post-mortem/KEY_DOWN-good.png" alt="" /></p>

<p>During each pulse (in blue) the infrared transmitter is switching on and off at
a certain carrier frequency, usually 38kHz. You can’t actually see the carrier
signal in these diagrams. During the spaces, the infrared transmitter isn’t
sending anything at all.</p>

<p>We set up a RedRat3 as an infrared <em>receiver</em>, and used the <a href="http://manpages.ubuntu.com/manpages/trusty/man1/mode2.1.html">mode2</a> tool from
<a href="http://www.lirc.org/">lirc</a> to log the pulses &amp; spaces for each keypress as we ran our testcase.</p>

<p>Here are a good keypress and a bad keypress from one of the test-runs:</p>

<p><img src="https://stb-tester.com/blog/images/ir-post-mortem/KEY_DOWN-good-and-bad.png" alt="" /></p>

<p>In the bad signal (the bottom one), about halfway through, it looks like the
pulses start slightly earlier than the good signal. It also seems that the
effect is cumulative, so towards the end of the signal the pulses get earlier
and earlier.</p>

<p>After looking at several bad signals there were no other obvious differences,
so we turned to some statistical measures. Here’s a histogram showing the mean
difference between the ideal &amp; actual pulse or space, within a given keypress:</p>

<p><img src="https://stb-tester.com/blog/images/ir-post-mortem/histogram-pulse-error.png" alt="" /></p>

<p>The x axis is in microseconds. The green and red lines represent the good &amp; bad
keypresses from the stb-tester ONE’s infrared transmitter. There’s no obvious
difference between the two groups of signals – maybe this is because in a bad
keypress it only takes one bad pulse to cause the Roku to miss the keypress,
but for a given keypress we’re measuring the average error across <em>all</em> the
pulses &amp; spaces for that keypress.</p>

<p>What <em>is</em> clear, however, is that there is a lot of variability to the duration
of these pulses &amp; spaces. Compare to the blue data, which is using a RedRat3 as
the transmitter: Its pulses &amp; spaces have much less variation from the ideal
duration.</p>

<p>A histogram of the total signal length shows a similar curve, which confirms
that variations in the length of one pulse have a knock-on effect on the rest
of the signal:</p>

<p><img src="https://stb-tester.com/blog/images/ir-post-mortem/histogram-signal-duration.png" alt="" /></p>

<p>At this point in the investigation, our best theory is: There is too much
variability in our infrared transmitter’s output, and this is causing the
missed keypresses. This could explain why some set-top boxes are affected more
than others – perhaps their decoding algorithm is more or less tolerant of
variations in the signal.</p>

<h3 id="a-minimal-testcase-generating-a-plain-carrier-frequency">A minimal testcase: Generating a plain carrier frequency</h3>

<p>Can we see the same variability in the carrier signal itself? The data we
captured with mode2, above, doesn’t tell us the carrier frequency so we’ll need
an Oscilloscope.</p>

<p>After purchasing a <a href="https://sigrok.org/wiki/Hantek_DSO-2090">Hantek DSO-2090</a> and making a few
<a href="https://github.com/OpenHantek/openhantek/pulls?q=is%3Apr+author%3Adrothlis">patches</a> to <a href="https://github.com/OpenHantek/openhantek">OpenHantek</a>, we could see the raw signal
coming out of our infrared transmitter. Here is what happens when we send a
single pulse of a fixed length, repeatedly:</p>

<iframe src="https://player.vimeo.com/video/167244381" width="640" height="361" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>

<p>You can see that the total length of the pulse varies quite a bit, and the
carrier signal within the pulse isn’t clean at all.</p>

<p>Our infrared transmitter used the FTDI <a href="http://www.ftdichip.com/Products/ICs/FT232R.htm">FT232R</a> chip, which is a USB to serial
converter. The chip has two modes of operation: RS-232, and bit-bang. In
bit-bang mode you specify a clock rate, and send it a sequence of ones and
zeros; the chip writes these ones and zeros to its output pins at the specified
clock rate. Unlike RS-232, there are no control bits (start bit, stop bit,
parity bit) so we have complete control over the signal.</p>

<p>In theory we would set the clock rate to twice the infrared protocol’s carrier
frequency, and send 1-0-1-0-1-0-etc to generate the carrier signal. In
practice, the FT232R’s bit-bang mode
<a href="http://www.huitsing.nl/irftdi/">only works at certain clock rates</a> so we use a higher clock rate and
send, for example, 1-1-1-1-1-0-0-0-0-0-1-1-1-1-1-etc (or however many ones we
need to generate the right carrier frequency).</p>

<p>To analyse the chip’s behaviour in the simplest way possible, we wrote a simple
C program using <a href="http://www.intra2net.com/en/developer/libftdi/">libftdi</a> that sets the clock rate and sends
1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0-1-0
(20 ones and zeros). Then we re-wrote our test program so that it uses FTDI’s
own <a href="http://www.ftdichip.com/Drivers/D2XX.htm">D2XX</a> SDK instead of libftdi. This is what we used to generate the signal
you saw in the oscilloscope video, above.</p>

<p>We bought some new chips directly from FTDI, just in case our manufacturer was
using counterfeit chips. Nope, the output is still bad. (Sorry manufacturer for
doubting your supply chain skills.)</p>

<p>FTDI’s <a href="http://www.ftdichip.com/Support/Documents/TechnicalNotes/TN_120_FT232R%20Errata%20Technical%20Note.pdf">FT232R Errata Technical Note</a> acknowledges that this is a known problem
with Revision A of the chip, but claims that it has been fixed in Revision B.
Our chips are Revision C, which is supposed to be <a href="http://www.ftdichip.com/Support/Documents/ProductChangeNotifications/PCN_FT_008.pdf">identical</a> to
Revision B. But we have proved that big-bang mode is still unreliable in
Revision C of the FT232R chip. We sent our code &amp; data to FTDI support, who
acknowledged the bug.</p>

<h3 id="the-solution-ftdi-ft230x">The solution: FTDI FT230X</h3>

<p>The FTDI <a href="http://www.ftdichip.com/Products/ICs/FT230X.html">FT230X</a> is a similar USB-to-serial chip that also has a bit-bang
mode, and most importantly, has a stable clock. Even better, we can set any
clock rate we want (to match the desired carrier frequency of our infrared
signal) so that we can minimise the necessary USB bandwidth (we only need to
send 1-0-1-0… instead of 1-1-1-1-1-0-0-0-0-0-1-1-1-1-1…).</p>

<p>With the FT230X, the signal as seen on an oscilloscope is perfectly stable, and
our Roku testcase passes 100% of the time – we have tested more than 500,000
keypresses without seeing a single missed keypress.</p>

<p>We have written a <a href="https://sourceforge.net/p/lirc/tickets/184/">lirc driver</a> for the FT230X, which is now on the lirc master
branch, in case anyone else wants to build their own USB infrared transmitter
based on the FT230X. We are proud to have created the first reliable,
low-powered, infrared transmitter on the market in a USB dongle form-factor.</p>

<p>If you’re a customer of ours and you have an stb-tester ONE, we will be posting
you a replacement infrared transmitter within the next few days. All you have
to do is install the <a href="https://stb-tester.com/manual/release-notes#v24-7">v24.7 software update</a> on your stb-tester ONE, unplug
the old infrared transmitter, and plug in the new one.</p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>New videos &amp; tutorials
</title>
        <pubDate>Mon, 29 Feb 2016 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2016/02/29/new-videos</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2016/02/29/new-videos</guid>
        <description>
          &lt;p&gt;We have just published three videos showcasing the stb-tester ONE, including a
detailed guide for measuring channel change times on a set-top box:&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>We have just published three videos showcasing the stb-tester ONE, including a
detailed guide for measuring channel change times on a set-top box:</p>

<ul>
  <li>
    <p><strong>Introduction to the stb-tester ONE:</strong> Physical setup of the HDMI &amp; infrared
connections; using the web interface for manual control and running automated
testcases; demonstration of an example testcase against a Roku set-top box;
using the interactive results interface.</p>
  </li>
  <li>
    <p><strong>Creating new testcases:</strong> The <em>stbt</em> Python API; capturing screenshots from
the device-under-test and adding them to your test-pack; using the <em>git</em>
version-control tool to manage testcases and deploy them to the stb-tester
ONE.</p>
  </li>
  <li>
    <p><strong>Measuring channel change time:</strong> Measuring channel change time on a set-top
box, then using Jupyter notebook to explore the data interactively and
generate a histogram of the measurements.</p>
  </li>
</ul>

<p>All three videos are available on our <a href="https://stb-tester.com/videos/">Videos &amp; tutorials</a> page. We
have many more tutorials planned, so stay tuned –
<a href="https://stb-tester.com/blog/">subscribe to this blog</a> or
<a href="https://twitter.com/stb_tester">follow us on twitter</a> for updates.</p>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>New case study: stb-tester.com supports YouView’s launch on Sony Bravia TVs
</title>
        <pubDate>Fri, 18 Dec 2015 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2015/12/18/new-case-study</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2015/12/18/new-case-study</guid>
        <description>
          &lt;p&gt;In November 2015 YouView launched on a range of Sony Bravia televisions across
the UK. The biggest technical challenges of the project were in the
integration, with frequent releases from YouView, Sony, MediaTek, and Google.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>In November 2015 YouView launched on a range of Sony Bravia televisions across
the UK. The biggest technical challenges of the project were in the
integration, with frequent releases from YouView, Sony, MediaTek, and Google.</p>

<p>Read <a href="https://stb-tester.com/case-study-youview-sony">how stb-tester.com supported YouView’s launch on Sony Bravia TVs</a>.</p>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Stb-tester.com to help DTVKit with test automation solution
</title>
        <pubDate>Wed, 02 Dec 2015 00:00:00 +0000</pubDate>
        <link>https://stb-tester.com/blog/2015/12/02/stb-tester.com-to-help-dtvkit-with-test-automation-solution</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2015/12/02/stb-tester.com-to-help-dtvkit-with-test-automation-solution</guid>
        <description>
          &lt;p&gt;Stb-tester.com is pleased to announce that DTVKit will be using our
stb-tester tool as part of their test automation strategy, and we will
be providing our expertise and support as a Partner Member of DTVKit.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester.com is pleased to announce that DTVKit will be using our
stb-tester tool as part of their test automation strategy, and we will
be providing our expertise and support as a Partner Member of DTVKit.</p>

<p><a href="http://www.dtvkit.org/">DTVKit</a> provides proven royalty-free DVB, MHEG, CI+, and HbbTV stacks
offered through a shared source code model that is membership based and
not-for-profit.</p>

<blockquote>
  <p>Stb-tester provides the perfect solution to allow automated system level
testing of DTVKit, not only for our own testing, but also enabling us to make
our testing scripts available for use to DTVKit members.
<small>Phil Evans, CEO, DTVKit.</small></p>
</blockquote>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Improve black-box testing agility: meet the Frame Object pattern
</title>
        <pubDate>Tue, 08 Sep 2015 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2015/09/08/meet-the-frame-object-pattern</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2015/09/08/meet-the-frame-object-pattern</guid>
        <description>
          &lt;p&gt;In the &lt;a href=&quot;https://stb-tester.com/blog/2015/09/02/applying-the-page-object-pattern-with-stb-tester&quot;&gt;previous blog post&lt;/a&gt; we discussed the [Page Object pattern] and how it
could be used to improve the readability of your test scripts. In this blog
post we introduce the Frame Object pattern, a specialisation of the Page Object
pattern that can be especially effective in stb-tester’s style of black-box UI
testing&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Frame Objects can reduce the maintenance cost of your test-packs,
particularly when the UI-under-test changes.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;By “black-box UI testing” we mean that you’re testing what the user &lt;em&gt;sees&lt;/em&gt;, by capturing a video stream of the rendered UI and using image processing to check it. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;

        </description>
        <content:encoded><![CDATA[
          <p>In the <a href="https://stb-tester.com/blog/2015/09/02/applying-the-page-object-pattern-with-stb-tester">previous blog post</a> we discussed the <a href="http://martinfowler.com/bliki/PageObject.html">Page Object pattern</a> and how it
could be used to improve the readability of your test scripts. In this blog
post we introduce the Frame Object pattern, a specialisation of the Page Object
pattern that can be especially effective in stb-tester’s style of black-box UI
testing<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. Frame Objects can reduce the maintenance cost of your test-packs,
particularly when the UI-under-test changes.</p>

<figure>

  <p><img src="https://stb-tester.com/blog/images/page-object/frame-object-pattern.png" /></p>

  <figcaption style="margin:20px 0">
    <p>Based on Martin Fowler’s diagram for <a href="http://martinfowler.com/bliki/PageObject.html">Page Objects in HTML UI testing</a></p>
  </figcaption>

</figure>

<p>A Frame Object is a class that extracts information from a frame of video,
typically by calling <code class="language-plaintext highlighter-rouge">stbt.ocr()</code> or <code class="language-plaintext highlighter-rouge">stbt.match()</code>. All the rest of your
testing code then uses these objects. A Frame Object translates from the
vocabulary of low-level image processing functions and regions (like
<code class="language-plaintext highlighter-rouge">stbt.ocr(region=stbt.Region(213, 23, 200, 36))</code>) to the vocabulary of
high-level features and user-facing concepts (like <code class="language-plaintext highlighter-rouge">programme_title</code>).</p>

<p>Here’s an example of a Frame Object (<code class="language-plaintext highlighter-rouge">GuideFrame</code>) and a test-case that uses it:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">GuideFrame</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">frame</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">frame</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">get_frame</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">frame</span>

    <span class="k">def</span> <span class="nf">__nonzero__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">is_visible</span>

    <span class="k">def</span> <span class="nf">__repr__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">is_visible</span><span class="p">:</span>
            <span class="nf">return </span><span class="p">(</span><span class="s">"GuideFrame(is_visible=True, programme_title=%r, "</span>
                    <span class="s">"current_time=%r)"</span><span class="p">)</span> <span class="o">%</span> <span class="p">(</span>
                    <span class="n">self</span><span class="p">.</span><span class="n">programme_title</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">current_time</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="s">"GuideFrame(is_visible=False)"</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">is_visible</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="nf">bool</span><span class="p">(</span><span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">'guide-header.png'</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">frame</span><span class="p">))</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">programme_title</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span>
            <span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="mi">213</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">36</span><span class="p">),</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">frame</span><span class="p">)</span>

    <span class="nd">@property</span>
    <span class="k">def</span> <span class="nf">current_time</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="n">m</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">'clock.png'</span><span class="p">,</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">frame</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span>
            <span class="n">region</span><span class="o">=</span><span class="n">m</span><span class="p">.</span><span class="n">region</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="mi">100</span><span class="p">),</span> <span class="n">frame</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">frame</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">open_guide</span><span class="p">():</span>
    <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_GUIDE'</span><span class="p">)</span>
    <span class="n">guide</span> <span class="o">=</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">wait_until</span><span class="p">(</span><span class="n">GuideFrame</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">guide</span>
    <span class="k">return</span> <span class="n">guide</span>


<span class="k">def</span> <span class="nf">test_that_guide_displays_the_correct_time</span><span class="p">():</span>
    <span class="n">guide</span> <span class="o">=</span> <span class="nf">open_guide</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">guide</span><span class="p">.</span><span class="n">current_time</span> <span class="o">==</span> <span class="n">datetime</span><span class="p">.</span><span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">().</span><span class="nf">strptime</span><span class="p">(</span><span class="s">'%H:%M'</span><span class="p">)</span></code></pre>
</figure>

<p>This will seem very much like a Page Object – it acts as a layer of
abstraction on top of the low-level image processing functions. Unlike a Page
Object it contains no behaviours or any means to stimulate the device under
test:</p>

<ul>
  <li>There are no implicit dependencies on time or external stimulus once
constructed.</li>
  <li>There no calls to <code class="language-plaintext highlighter-rouge">stbt.get_frame()</code> (except in the constructor). We pass
<code class="language-plaintext highlighter-rouge">self.frame</code> into every call of <code class="language-plaintext highlighter-rouge">stbt.match()</code>, <code class="language-plaintext highlighter-rouge">stbt.ocr()</code> or any other
image-processing function.</li>
  <li>We perform no image-processing or other expensive work in the constructor -
this object is very cheap to construct.</li>
  <li>The object is <a href="https://en.wikipedia.org/wiki/Immutable_object">immutable</a> – there are no methods to change the externally
visible state of the object. If you want an updated view of the
system-under-test’s state you can construct a new object for each frame.</li>
  <li>There are no calls to <code class="language-plaintext highlighter-rouge">stbt.wait_until()</code> or <code class="language-plaintext highlighter-rouge">time.sleep()</code> - this object
can’t change so there is no point waiting.</li>
  <li>There are no calls to <code class="language-plaintext highlighter-rouge">stbt.press()</code> or anything else that affects the device
under test.</li>
</ul>

<p>These may seem like arbitrary or unreasonable restrictions but they provide
some great benefits:</p>

<ul>
  <li>
    <p><strong>Testability</strong>: Unlike full Page Objects, Frame Objects can be tested very
cheaply.  All you need is a corpus of example screenshots.  This is because
Frame Objects behave in an entirely deterministic way based on the parameters
provided to the constructor (typically the only parameter is a frame of
video).  Most significantly, Frame Objects don’t depend on the dynamic
behaviour of the device-under-test which is far more difficult to capture than
a few screenshots.</p>

    <p>By providing a <a href="https://docs.python.org/2/reference/datamodel.html#object.__repr__"><code class="language-plaintext highlighter-rouge">__repr__</code> method</a> you can test these objects using
<a href="https://docs.python.org/2/library/doctest.html">doctests</a>.  This opens up other possibilities, which we will cover in a
future blog post.</p>
  </li>
  <li>
    <p><strong>Agility</strong>: Because of their improved testability, Frame Objects are cheap to
maintain and easy to evolve.  If the cosmetics of the application under test
change, or you find a bug in your Frame Object, just capture a screenshot of
that situation and add it to your test corpus.  You can then fix it with
confidence:</p>

    <ul>
      <li>Confidence that the updated Frame Object fixes the issue – you can verify
this by applying your new Frame Object to the new screenshots added to
your corpus.</li>
      <li>Confidence that you haven’t introduced regressions with your fixes – you
can see that your Frame Object behaves the same when run against your
existing corpus of screenshots.</li>
      <li>Confidence that you haven’t changed your expectations of the behaviour of
the device-under-test – the Frame Object contains no behavioural
expectations.</li>
    </ul>
  </li>
  <li>
    <p><strong>Consistency</strong>: Calling <code class="language-plaintext highlighter-rouge">stbt.match</code> or <code class="language-plaintext highlighter-rouge">stbt.ocr</code> multiple times to extract
information from a frame can lead to confusing inconsistencies if each
function sees a different frame. The Frame Object Pattern avoids this by
capturing a frame exactly once.</p>
  </li>
  <li>
    <p><strong>Performance</strong>: Frame Objects extract required information from the frame
when needed, rather than in advance. This can avoid doing a lot of expensive
image matching or OCR when it’s not necessary. Because the object is
immutable and each property or method on the object is deterministic you can
apply <a href="http://stackoverflow.com/questions/1988804/what-is-memoization-and-how-can-i-use-it-in-python">memoisation</a>, allowing you to write tests in a natural manner
without a performance penalty.</p>
  </li>
</ul>

<p>So that’s all well and good, but what about the behaviours that you removed?
After all they are usually the thing you want to test in the underlying device.
It turns out that the Page Object and the Frame Object patterns are actually
complementary rather than conflicting.  Where necessary you can still have
page objects, but they use Frame Objects to extract the information from the
underlying frames.</p>

<p>Two classes rather than one sounds complicated?  In our experience the page
object is no longer necessary in most cases and you can get away with just
using helper functions or using the Frame Objects directly from test cases. But
when you have more complex interactions and your helper functions need to store
state you can still create page objects.  These page objects will use Frame
Objects: the additional overall complexity is more than offset by the reduction
in complexity in the more expensive to maintain page objects – an overall win
for maintainability.</p>

<p>For example here’s a helper function that works in conjunction with the above
Frame Object:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">select_next_programme</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">direction</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
    <span class="n">key</span> <span class="o">=</span> <span class="p">{</span><span class="o">-</span><span class="mi">1</span><span class="p">:</span> <span class="s">'KEY_LEFT'</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span> <span class="s">'KEY_RIGHT'</span><span class="p">}[</span><span class="n">direction</span><span class="p">]</span>
    <span class="n">original_title</span> <span class="o">=</span> <span class="nc">GuideFrame</span><span class="p">().</span><span class="n">programme_title</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
        <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
        <span class="k">if</span> <span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nc">GuideFrame</span><span class="p">().</span><span class="n">programme_title</span> <span class="o">!=</span> <span class="n">original_title</span><span class="p">,</span>
                      <span class="n">timeout_secs</span><span class="o">=</span><span class="mi">3</span><span class="p">):</span>
            <span class="k">return</span> <span class="nc">GuideFrame</span><span class="p">()</span>


<span class="k">def</span> <span class="nf">test_that_we_can_scroll_back_and_forth_through_programmes_in_the_guide</span><span class="p">():</span>
    <span class="n">guide</span> <span class="o">=</span> <span class="nf">open_guide</span><span class="p">()</span>
    <span class="n">original_title</span> <span class="o">=</span> <span class="n">guide</span><span class="p">.</span><span class="n">programme_title</span>
    <span class="k">assert</span> <span class="n">original_title</span>

    <span class="n">new_title</span> <span class="o">=</span> <span class="nf">select_next_programme</span><span class="p">(</span><span class="n">direction</span><span class="o">=</span><span class="mi">1</span><span class="p">).</span><span class="n">programme_title</span>
    <span class="k">assert</span> <span class="n">new_title</span>

    <span class="k">assert</span> <span class="nf">select_next_programme</span><span class="p">(</span><span class="n">direction</span><span class="o">=-</span><span class="mi">1</span><span class="p">).</span><span class="n">programme_title</span> <span class="o">==</span> <span class="n">original_title</span></code></pre>
</figure>

<p>So: <strong>the look of your device is captured by frame objects</strong> and <strong>the behaviour
of the device is captured by helper functions and page objects</strong>.  This works
out very well; in our experience the look of a UI will tend to change a lot
during development, whereas the underlying behaviour changes at a much slower
rate.</p>

<p><strong>Frame Objects checklist:</strong></p>

<ol>
  <li>Take a frame in the constructor.</li>
  <li>Provide properties describing the contents of that frame.</li>
  <li>Contain no actions or behaviours.</li>
  <li>Include a <code class="language-plaintext highlighter-rouge">__repr__</code> method that prints as much data from the frame as
possible to enable use with doctests and logging.</li>
  <li>Include a <code class="language-plaintext highlighter-rouge">__nonzero__</code> method dictating whether the current frame is
suitable – for use with <code class="language-plaintext highlighter-rouge">stbt.wait_until</code>.</li>
</ol>

<p>We’re considering adding specific support for Frame Objects in stb-tester to
help reduce the amount of boilerplate required.</p>

<dl>
  <dt>Update 2016-03-10:</dt>
  <dd>A <code class="language-plaintext highlighter-rouge">FrameObject</code> base class was merged to stb-tester git master and will be
in the stb-tester v25 release.  It reduces the boilerplate involved in
Frame Objects by taking a frame in the constructor, and defining <code class="language-plaintext highlighter-rouge">__repr__</code>
and <code class="language-plaintext highlighter-rouge">__nonzero__</code> for you.  So now all you have to do is derive from
<code class="language-plaintext highlighter-rouge">stbt.FrameObject</code> and define some properties.  See <a href="https://github.com/stb-tester/stb-tester/pull/332">PR #332</a> for more
information.</dd>
</dl>

<p><a href="https://groups.google.com/d/msg/stb-tester/D0nkBhHKYtY/bj3wdacUAQAJ">Discuss this on our mailing list</a></p>

<p><strong>Footnotes:</strong></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>By “black-box UI testing” we mean that you’re testing what the user <em>sees</em>, by capturing a video stream of the rendered UI and using image processing to check it. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>

        ]]></content:encoded>
      </item>
    
      <item>
        <title>Applying the &quot;page object&quot; pattern with stb-tester
</title>
        <pubDate>Wed, 02 Sep 2015 00:00:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2015/09/02/applying-the-page-object-pattern-with-stb-tester</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2015/09/02/applying-the-page-object-pattern-with-stb-tester</guid>
        <description>
          &lt;p&gt;Often you will hear us recommend that you should structure your test-packs as a
set of library functions, plus test-cases that use those library functions. In
this blog post we attempt to make this recommendation more concrete by
demonstrating how to apply the &lt;a href=&quot;http://martinfowler.com/bliki/PageObject.html&quot;&gt;“page object” pattern&lt;/a&gt; when writing
stb-tester test-cases.&lt;/p&gt;


        </description>
        <content:encoded><![CDATA[
          <p>Often you will hear us recommend that you should structure your test-packs as a
set of library functions, plus test-cases that use those library functions. In
this blog post we attempt to make this recommendation more concrete by
demonstrating how to apply the <a href="http://martinfowler.com/bliki/PageObject.html">“page object” pattern</a> when writing
stb-tester test-cases.</p>

<p>Here’s what a test-case can look like when applying the page-object pattern:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_that_bbc_one_shows_the_one_show_at_7pm</span><span class="p">():</span>
    <span class="n">guide</span> <span class="o">=</span> <span class="nf">open_guide</span><span class="p">()</span>
    <span class="n">guide</span><span class="p">.</span><span class="nf">enter_channel</span><span class="p">(</span><span class="mi">101</span><span class="p">)</span>
    <span class="n">guide</span><span class="p">.</span><span class="nf">scroll_to</span><span class="p">(</span><span class="n">time</span><span class="o">=</span><span class="s">'19:00'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">guide</span><span class="p">.</span><span class="nf">find_selected_programme</span><span class="p">()</span> <span class="o">==</span> <span class="s">"The One Show"</span></code></pre>
</figure>

<p>and this is what it can look like without:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_that_bbc_one_shows_the_one_show_at_7pm</span><span class="p">():</span>
    <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_GUIDE'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">'guide-header.png'</span><span class="p">))</span>

    <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_1'</span><span class="p">)</span>
    <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_0'</span><span class="p">)</span>
    <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_1'</span><span class="p">)</span>
    <span class="n">time</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>

    <span class="k">while</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="mi">213</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">36</span><span class="p">))</span> <span class="o">&lt;</span> <span class="s">"19:00"</span><span class="p">:</span>
        <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_RIGHT'</span><span class="p">)</span>
        <span class="n">time</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

    <span class="k">assert</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="mi">213</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">36</span><span class="p">))</span> <span class="o">==</span> <span class="s">"The One Show"</span></code></pre>
</figure>

<p>The improvement in understandability is clear. Without the page-object, the
meaning of your test-case is buried in the forest of calls to <code class="language-plaintext highlighter-rouge">stbt.match</code>,
<code class="language-plaintext highlighter-rouge">ocr</code>, <code class="language-plaintext highlighter-rouge">press</code> and <code class="language-plaintext highlighter-rouge">wait_until</code>. Using the page-object pattern means that your
test-case consists of high level concepts that expose the intent of the
test-case. Notice that with the page-object pattern <strong>calls to <code class="language-plaintext highlighter-rouge">stbt</code> functions
almost never appear in test-cases directly</strong>.</p>

<figure>

  <p><img src="https://stb-tester.com/blog/images/page-object/page-object-pattern.png" /></p>

  <figcaption style="margin:20px 0">
    <p>Based on Martin Fowler’s diagram for <a href="http://martinfowler.com/bliki/PageObject.html">Page Objects in HTML UI testing</a></p>
  </figcaption>

</figure>

<p>In this example the page-object is <code class="language-plaintext highlighter-rouge">guide</code>. This is an instance of a class that
knows how to understand and navigate your set-top-box’s programme guide.</p>

<p>The class might look like this:</p>

<figure class="highlight">
  <pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">Guide</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">find_selected_programme</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="mi">213</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">36</span><span class="p">))</span>

    <span class="k">def</span> <span class="nf">read_lcn</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">20</span><span class="p">))</span>

    <span class="k">def</span> <span class="nf">enter_channel</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">lcn</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="s">"%03d"</span> <span class="o">%</span> <span class="n">lcn</span><span class="p">:</span>
            <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_%s'</span> <span class="o">%</span> <span class="n">x</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nf">int</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">read_lcn</span><span class="p">())</span> <span class="o">==</span> <span class="n">lcn</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">read_programme_time</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">ocr</span><span class="p">(</span><span class="n">region</span><span class="o">=</span><span class="n">stbt</span><span class="p">.</span><span class="nc">Region</span><span class="p">(</span><span class="mi">213</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">36</span><span class="p">))</span>

    <span class="k">def</span> <span class="nf">scroll_to</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">time</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
            <span class="n">orig_time</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">read_programme_time</span><span class="p">()</span>
            <span class="k">if</span> <span class="n">orig_time</span> <span class="o">==</span> <span class="n">time</span><span class="p">:</span>
                <span class="k">return</span>
            <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_RIGHT'</span><span class="p">)</span>
            <span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">self</span><span class="p">.</span><span class="nf">read_programme_time</span><span class="p">()</span> <span class="o">!=</span> <span class="n">orig_time</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">assert</span> <span class="bp">False</span>


<span class="k">def</span> <span class="nf">open_guide</span><span class="p">():</span>
    <span class="n">stbt</span><span class="p">.</span><span class="nf">press</span><span class="p">(</span><span class="s">'KEY_GUIDE'</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">wait_until</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">stbt</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="s">'guide-header.png'</span><span class="p">))</span>
    <span class="k">return</span> <span class="nc">Guide</span><span class="p">()</span></code></pre>
</figure>

<p>The class is used by many test-cases, which increases code reuse. It serves as
a central place to handle any known quirks of your UI, and as a single place to
update if your UI changes.</p>

<p>If you need to test multiple similar (but slightly different) variants of your
UI with a single test-pack, this class is the place to abstract over those
differences.</p>

<p>Note that I made <code class="language-plaintext highlighter-rouge">open_guide</code> a standalone function rather than a method or
static method of class <code class="language-plaintext highlighter-rouge">Guide</code>. I don’t consider navigating <em>to</em> the guide to
belong to the <code class="language-plaintext highlighter-rouge">Guide</code> page-object. Rather it belongs to the app as a whole. The
rule of thumb is that the methods on the <code class="language-plaintext highlighter-rouge">Guide</code> class are only valid to call
if the guide is currently visible.</p>

<p>So, using the page-object pattern:</p>

<ul>
  <li>Improves the readability of your test-cases.</li>
  <li>Reduces the maintenance cost of your test-pack by sharing (and thus reducing)
test code and providing a central place to apply fixes and updates.</li>
  <li>Assists with reusing test-cases for testing multiple variants of a single UI.</li>
</ul>

<p>In <a href="https://stb-tester.com/blog/2015/09/08/meet-the-frame-object-pattern">next week’s blog post</a> we will discuss an extension to this approach called
the “Frame Object Pattern” which can improve the maintainability of your test
scripts even further.</p>

<p><a href="https://groups.google.com/d/msg/stb-tester/r9Sweogo-jM/RuhYMfI1BwAJ">Discuss this on our mailing list</a></p>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Creating LIRC config files - part 1: theory</title>
        <pubDate>Tue, 10 Jun 2014 23:02:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2014/06/10/creating-lirc-config-files-part-1-theory</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2014/06/10/creating-lirc-config-files-part-1-theory</guid>
        <description>
          &lt;dl&gt;
  &lt;dt&gt;TL;DR version:&lt;/dt&gt;
  &lt;dd&gt;stb-tester reqires a LIRC config file to know how to translate key names
into IR pulses.  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;irrecord&lt;/code&gt; can be used to create them by recording IR
signals but can have difficulty sometimes.  In those cases you can provide
additional information to make recording succeed which can be determined
from the output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mode2&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xmode2&lt;/code&gt; and by consulting the &lt;a href=&quot;http://winlirc.sourceforge.net/technicaldetails.html&quot;&gt;lirc config
file documentation&lt;/a&gt;.&lt;/dd&gt;
&lt;/dl&gt;


        </description>
        <content:encoded><![CDATA[
          <dl>
  <dt>TL;DR version:</dt>
  <dd>stb-tester reqires a LIRC config file to know how to translate key names
into IR pulses.  <code class="language-plaintext highlighter-rouge">irrecord</code> can be used to create them by recording IR
signals but can have difficulty sometimes.  In those cases you can provide
additional information to make recording succeed which can be determined
from the output of <code class="language-plaintext highlighter-rouge">mode2</code> and <code class="language-plaintext highlighter-rouge">xmode2</code> and by consulting the <a href="http://winlirc.sourceforge.net/technicaldetails.html">lirc config
file documentation</a>.</dd>
</dl>

<p><em>This is part 1 of a 2 part blog post.  It focuses on how stb-tester uses LIRC
to produce IR signals and how recording works in theory.  Part 2 will apply this
understanding to successfully record remote control codes where <code class="language-plaintext highlighter-rouge">irrecord</code> fails
by default.</em></p>

<h3 id="overview-of-stb-tester-and-ir">Overview of stb-tester and IR</h3>

<p>stb-tester uses LIRC (Linux Infrared Remote Control) to send IR signals to
control the set-top-boxes and smart-tvs under test.  The test scripts refer to
key names like “KEY_0” and “MENU”, but the IR blaster has to send out the pulses
of IR light that the set-top-box/TV will understand.</p>

<p><img src="https://stb-tester.com/blog/images/lirc/lirc.svg" alt="" /></p>

<p>LIRC does this conversion from key names to pulses based on a configuration file
you provide it that describes the IR protocol to be used and what the valid key
values are.  You can use the lirc command <code class="language-plaintext highlighter-rouge">irrecord</code> to create a configuration
file by pressing buttons on the remote.</p>

<p><img src="https://stb-tester.com/blog/images/lirc/irrecord.svg" alt="" /></p>

<p>Unfortunately this doesn’t always work perfectly.  To see why let’s look a
little more in detail at how lirc calculates what pulses to send:</p>

<p><img src="https://stb-tester.com/blog/images/lirc/lirc-zoom.svg" alt="" /></p>

<p>The codes section of the config file describes how to map a key name (such as
“KEY_1” or “KEY_MENU” to a number.  This number will be encoded into a series of
pulses and spaces.  In the case in the diagram above 1s are encoded as long
pulses and 0s as short pulses.</p>

<h3 id="recording-ir-codes">Recording IR codes</h3>

<p>irrecord works in reverse: it will record the signals from your remote control
and create a config file for lircd:</p>

<p><img src="https://stb-tester.com/blog/images/lirc/irrecord-zoom.svg" alt="" /></p>

<ol>
  <li>It first tries to derive the encoding parameters and the codes by getting the
user to press lots of different buttons and analysing the pulse/space
timings.</li>
  <li>It then works out the code mapping by getting the user to enter the name of a
button and then pressing the button on the remote.</li>
</ol>

<p>Creating the code mapping is very easy to do once you know the encoding, but
automatically working out the encoding parameters is more difficult and so can
be unreliable.  When this happens irrecord may fail to produce a config file or
the config file it does produce may be ineffective.</p>

<p><code class="language-plaintext highlighter-rouge">irrecord</code> will often succeed where it had failed before if you give it a
helping-hand by deriving the encoder parameters for it.  In the next blog post I
describe how I determined the encoding parameters for a SONY Bravia smart TV
remote control, and thus was able to create a full lirc config for this remote
control.</p>

<p><strong>References:</strong></p>

<ul>
  <li><a href="http://winlirc.sourceforge.net/technicaldetails.html">WinLIRC Configuration File Format</a></li>
</ul>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>Improving OCR accuracy</title>
        <pubDate>Mon, 14 Apr 2014 15:52:00 +0100</pubDate>
        <link>https://stb-tester.com/blog/2014/04/14/improving-ocr-accuracy</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2014/04/14/improving-ocr-accuracy</guid>
        <description>
          &lt;dl&gt;
  &lt;dt&gt;TL;DR version:&lt;/dt&gt;
  &lt;dd&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stbt.ocr&lt;/code&gt;’s misreadings aren’t due to it not being familiar with your
fonts.  The root cause is that the OCR system we use (&lt;a href=&quot;https://code.google.com/p/tesseract-ocr/&quot;&gt;tesseract&lt;/a&gt;) is
intended for reading the printed word.  With this new understanding we’ve
made improvements to OCR resulting in a 12× reduction of errors.  This will
be in the next stb-tester release.&lt;/dd&gt;
&lt;/dl&gt;


        </description>
        <content:encoded><![CDATA[
          <dl>
  <dt>TL;DR version:</dt>
  <dd><code class="language-plaintext highlighter-rouge">stbt.ocr</code>’s misreadings aren’t due to it not being familiar with your
fonts.  The root cause is that the OCR system we use (<a href="https://code.google.com/p/tesseract-ocr/">tesseract</a>) is
intended for reading the printed word.  With this new understanding we’ve
made improvements to OCR resulting in a 12× reduction of errors.  This will
be in the next stb-tester release.</dd>
  <dt>Update 2014-04-23:</dt>
  <dd><a href="https://github.com/stb-tester/stb-tester/pull/146">Improvements</a> have now been merged to master and so will be in
stb-tester 0.20.</dd>
</dl>

<p>stb-tester uses the open-source tesseract engine for OCR.  This works really
well but is not perfect.  Tesseract was was primarily designed to operate on
text which had been printed and then scanned.  This is broadly the same but
slightly different to OCR on the screen:</p>

<table class="table table-condensed">

<thead><tr><th>Scanned Text</th><th>Text on Screen</th></tr></thead>
<tbody>
<tr>
    <td>High resolution scans (300dpi)</td>
    <td>Lower-resolution anti-aliased fonts</td>
</tr>
<tr>
    <td>Text at an angle, perhaps curved</td><td>Text is perfectly straight</td>
</tr>
<tr>
    <td>Usually black text on a white (or near white) background</td>
    <td>Coloured text on coloured background often with gradients.</td>
</tr>
<tr>
    <td>Artifacts due to blobs of ink or dust, text not quite joining up and
        stretched or crinkled pages.</td>
    <td>Artefacts due to video compression caused by capture device (e.g. h264)
    </td>
</tr>
<tr>
    <td>Contains oddities related to the limitations of manually typeset text
        (e.g. ligatures)</td>
    <td>Fairly consistent, one glyph per character.</td>
</tr>
</tbody>
</table>

<p>Over the years tesseract has evolved techniques for dealing with each of the
problems listed on the left.  According to my experiments it is not yet
perfectly adapted for dealing with the issues on the right.</p>

<h3 id="measurement">Measurement</h3>

<p>But first: If we want to improve something, first we must be able to measure it.
Fortunately the YouView UI which I’m using for this contains a 6163 word <a href="http://www.youview.com/terms-conditions/product/">Terms
and Conditions</a> and <a href="http://www.youview.com/privacy/product/">Privacy Policy</a> which is also available
online.  This gives me a basis for comparison.  I can then OCR the screens
and compare against the ideal with the word diff tool <a href="http://os.ghalkes.nl/dwdiff.html"><code class="language-plaintext highlighter-rouge">dwdiff</code></a>.  This
ignores formatting differences and will print statistics to measure how good a
job we’ve done:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>old: 6278 words  6046 96% common  0 0% deleted  232 3% changed
new: 6199 words  6046 97% common  0 0% inserted 153 2% changed
</code></pre></div></div>

<p>For this case the number included in the table below would be 232/6278.  e.g.
232 of the words of the original text were not correctly recognised.</p>

<p>The test script that I used to generate these results can be found on a <a href="https://github.com/wmanley/stb-tester/blob/train-tesseract/ocr/">branch
on github</a>.</p>

<h3 id="approaches-to-improve-accuracy">Approaches to improve accuracy</h3>

<h4 id="training">Training</h4>

<ul>
  <li><a href="https://code.google.com/p/tesseract-ocr/wiki/TrainingTesseract3">Training Tesseract 3</a> - Tesseract wiki</li>
</ul>

<p>The theory is that by training tesseract on the fonts you’re using in your
specific UI stb-tester will be able to do a better job at recognising text.  It
turns out <strong>that this isn’t true</strong>.  Training on the specific font used in the
YouView UI had no beneficial effect, in fact the opposite turned out to be the
case.  We hypothesise that a lot of effort has gone into the <code class="language-plaintext highlighter-rouge">eng.trainneddata</code>
file that tesseract ships with to make it work well with the majority of fonts
and text so training with a specific font has little effect.</p>

<p>Training turns out to be <a href="https://code.google.com/p/tesseract-ocr/wiki/TrainingTesseract3">much trickier</a> than you might expect -
despite the tesseract wiki proclaiming “And that’s all there is to it” after 12
pages of instructions.  Eventually I was able to write a script to automate
these instructions.  As a point of interest <a href="https://github.com/wmanley/stb-tester/blob/train-tesseract/ocr/training/tess-train.py">you can find this on my
train-tesseract branch on github</a>.  Instructions are included there.
As training was ineffective I’ve no intention to merge this to stb-tester
master at this time.</p>

<p>Result: <strong>No useful effect</strong> once other improvements are made, harmful in some
cases.</p>

<h4 id="remove-ligatures">Remove ligatures</h4>

<p>Ligatures are when two letters are combined into a single glyph.  e.g. fi might
be rendered as the single glyph (and unicode codepoint) ﬁ.  This was used in
traditional <a href="http://en.wikipedia.org/wiki/Movable_type">movable type</a>
typesetting to make typesetting easier and look better.</p>

<p>This is not really relevant for our case as we are interested in the content,
not the rendering.  I’ve tried two approaches to this:</p>

<ul>
  <li>Tell tesseract to not recognise ligatures with
<code class="language-plaintext highlighter-rouge">-c tessedit_char_blacklist=ﬀﬁﬂﬃﬄﬅﬆ</code>.  This is “+noligatures” below.</li>
  <li>Expand ligatures after tesseract has recognised them.  This is
“+replaceligatures” below.</li>
</ul>

<p>Result: Replacing ligatures is superior to blacklisting them.  Results in an
<strong>improvement of 0.4%</strong>.</p>

<h4 id="normalise-punctuation">Normalise Punctuation</h4>

<p>Unicode contains a whole bunch of punctuation which all look very similar.  We
consider for the purposes of set-top box testing our users are unlikely to be
interested in the differences between a hyphen (‐), a minus sign (−) and any of
the four types of unicode dash (‒, –, —, ―).  Nor will there be much interest
in the differences between the various types of quotation marks.</p>

<p>This approach cheats slightly as, strictly speaking, to match against some known
text you need to additionally normalise the expected text.  When this is merged
to master stbt will provide an API for doing this.</p>

<p>Result: <strong>Positive effect</strong> reducing error rate by ~0.3%</p>

<h4 id="scale-text-up">Scale text up</h4>

<p>Tesseract expects high resolution (300dpi) scans of text with no anti-aliasing.
Screen text on the other hand tends to be relatively low resolution (72dpi)
and anti-aliased.  tesseract’s first step “thresholds” the image - turning the
image into 1-bit monochrome black and white binary image.  This throws away any
information about the shapes of the letters provided by anti-aliasing before
performing outline recognition.</p>

<p>Ideally we would patch tesseract to directly take anti-aliasing into account
during outline recognition, but instead it seem that just scaling the image up
before thresholding allows this information to be preserved until the outline
extraction step.</p>

<p>Result: <strong>This provides the bulk of the improvement in error rate</strong>, bringing it
down from 8.3% to 1.2%.</p>

<p>There are many image scaling algorithms.  I tested against all the <a href="http://www.imagemagick.org/Usage/resize/">algorithms
available in imagemagick</a> at 2×, 3× and 4× scaling factor:</p>

<ul>
  <li>Bartlett</li>
  <li>Blackman</li>
  <li>Bohman</li>
  <li>Box</li>
  <li>Catrom</li>
  <li>Cosine</li>
  <li>Cubic</li>
  <li>Gaussian</li>
  <li>Hamming</li>
  <li>Hanning</li>
  <li>Hermite</li>
  <li>Jinc</li>
  <li>Kaiser</li>
  <li>Lagrange</li>
  <li>Lanczos</li>
  <li>LanczosSharp</li>
  <li>Lanczos2</li>
  <li>Lanczos2Sharp</li>
  <li>Mitchell</li>
  <li>Parzen</li>
  <li>Point</li>
  <li>Quadratic</li>
  <li>Robidoux</li>
  <li>RobidouxSharp</li>
  <li>Sinc</li>
  <li>SincFast</li>
  <li>Spline</li>
  <li>Triangle</li>
  <li>Welsh</li>
</ul>

<p>To cut a long story short the best algorithm is “Triangle” at 3× scaling.  This
turns out to be bilinear scaling which conveniently is the default interpolation
mode used by OpenCV’s <a href="http://docs.opencv.org/modules/imgproc/doc/geometric_transformations.html#resize"><code class="language-plaintext highlighter-rouge">resize</code></a> function (<code class="language-plaintext highlighter-rouge">cv2.INTER_LINEAR</code>).</p>

<p>3× worked better than 2× or 4× scaling.  My theory is that this is because each
pixel becomes 9 pixels (3×3) the sampling point in the middle can stay the same.
This theory is unsubstantiated speculation however.  Perhaps someone with a
greater understanding of re-sampling can explain this?</p>

<h4 id="sharpening">Sharpening</h4>

<p>The theory is that h264 encoding (such as performed by the TeradeK VidiU or the
Hauppauge HD-PVR) causes some blurring.  The sharpening is intended to make the
edges of the words sharper in the hope that tesseract’s thresholding algorithm
will then be able to do a better job.</p>

<p>Result: <strong>This had a small negative effect</strong>.</p>

<h3 id="results">Results</h3>

<p>With scaling and ligature and punctuation normalisation the error rate drops
from 8.3% to 0.65%.  This is a improvement of 12×.  An extract of the full
results is included below:</p>

<table>
  <thead>
    <tr>
      <th>Configuration</th>
      <th style="text-align: right">Error Count</th>
      <th style="text-align: right">Error rate</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Default</strong></td>
      <td style="text-align: right"><strong>523 / 6278</strong></td>
      <td style="text-align: right"><strong>8.33%</strong></td>
    </tr>
    <tr>
      <td>Normalising Punctuation</td>
      <td style="text-align: right">506 / 6278</td>
      <td style="text-align: right">8.06%</td>
    </tr>
    <tr>
      <td>3× Scaling</td>
      <td style="text-align: right">78 / 6278</td>
      <td style="text-align: right">1.24%</td>
    </tr>
    <tr>
      <td>Scaling and Replacing Ligatures</td>
      <td style="text-align: right">56 / 6278</td>
      <td style="text-align: right">0.89%</td>
    </tr>
    <tr>
      <td>Scaling and Replacing Punctuation</td>
      <td style="text-align: right">63 / 6278</td>
      <td style="text-align: right">1.00%</td>
    </tr>
    <tr>
      <td>Replacing Ligatures</td>
      <td style="text-align: right">492 / 6278</td>
      <td style="text-align: right">7.83%</td>
    </tr>
    <tr>
      <td>Replacing Punctuation</td>
      <td style="text-align: right">506 / 6278</td>
      <td style="text-align: right">8.06%</td>
    </tr>
    <tr>
      <td>Default training plus training on FS Me</td>
      <td style="text-align: right">516 / 6278</td>
      <td style="text-align: right">8.21%</td>
    </tr>
    <tr>
      <td>Training on FS Me font</td>
      <td style="text-align: right">696 / 6278</td>
      <td style="text-align: right">11.09%</td>
    </tr>
    <tr>
      <td><strong>Scaling and Replacing Ligatures and Punctuation</strong></td>
      <td style="text-align: right"><strong>41 / 6278</strong></td>
      <td style="text-align: right"><strong>0.65%</strong></td>
    </tr>
  </tbody>
</table>

<p><a href="https://stb-tester.com/blog/ocr-accuracy-results/table.html">Full results with example output can be found here</a>.</p>

<p>Admittedly this is all a little unscientific.  Further testing with different
text would be required to estimate error bars on the above measurements but I
believe this is conclusive enough to justify the improvements to stb-tester.</p>

<h3 id="thanks">Thanks</h3>

<p>Thanks to YouView for sponsoring this effort.</p>

<p>Further reading:</p>

<ul>
  <li><a href="http://research.google.com/pubs/archive/33418.pdf">An Overview of the Tesseract OCR Engine</a> - Ray Smith</li>
  <li><a href="https://code.google.com/p/tesseract-ocr/wiki/TrainingTesseract3">Training Tesseract 3</a> - Tesseract wiki</li>
  <li><a href="https://code.google.com/p/tesseract-ocr/wiki/ImproveQuality">Improving the quality of the output</a> - Tesseract wiki</li>
  <li><a href="http://tesseract-ocr.googlecode.com/files/TesseractOSCON.pdf">Tesseract OCR Engine - What it is, where it came from, where it is going</a> - Ray Smith - presented at OSCON 2007.</li>
</ul>


        ]]></content:encoded>
      </item>
    
      <item>
        <title>stb-tester at GTAC 2013</title>
        <pubDate>Wed, 01 May 2013 15:23:42 +0100</pubDate>
        <link>https://stb-tester.com/blog/2013/05/01/stb-tester-at-gtac-2013</link>
        <guid isPermaLink="true">https://stb-tester.com/blog/2013/05/01/stb-tester-at-gtac-2013</guid>
        <description>
          &lt;p&gt;Stb-tester co-founder David Röthlisberger gave a presentation at the Google
Test Automation Conference on how stb-tester was built and an overview of some
of the things you can achieve with stb-tester. See the &lt;a href=&quot;http://www.youtube.com/watch?v=Fdn2LxxM7wA&amp;amp;list=SPSIUOFhnxEiCODb8XQB-RUQ0RGNZ2yW7d&quot;&gt;video on
YouTube&lt;/a&gt;.&lt;/p&gt;

        </description>
        <content:encoded><![CDATA[
          <p>Stb-tester co-founder David Röthlisberger gave a presentation at the Google
Test Automation Conference on how stb-tester was built and an overview of some
of the things you can achieve with stb-tester. See the <a href="http://www.youtube.com/watch?v=Fdn2LxxM7wA&amp;list=SPSIUOFhnxEiCODb8XQB-RUQ0RGNZ2yW7d">video on
YouTube</a>.</p>

<p>For a write up of GTAC 2013 see <a href="http://lwn.net/Articles/548910/">Dave’s article on LWN</a>.</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/Fdn2LxxM7wA" title="YouTube video player" frameborder="0" allow="autoplay; encrypted-media; picture-in-picture" allowfullscreen=""></iframe>

        ]]></content:encoded>
      </item>
    
  </channel>
</rss>
