<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
        <title>Uncovered thoughts</title>
        <link>https://www.sergiofreire.com</link>
        <description>Sergio Freire's personal blog on software testing, agile.</description>
        <lastBuildDate>Wed, 13 May 2026 14:27:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Uncovered thoughts</title>
            <url>https://www.sergiofreire.com/favicon/favicon-96x96.png</url>
            <link>https://www.sergiofreire.com</link>
        </image>
        <copyright>All rights reserved 2021, Sergio Freire</copyright>
        <item>
            <title><![CDATA[AI vision in home automation]]></title>
            <link>https://www.sergiofreire.com/post/home-automation-with-ai-vision</link>
            <guid>https://www.sergiofreire.com/post/home-automation-with-ai-vision</guid>
            <pubDate>Tue, 28 Nov 2023 12:30:00 GMT</pubDate>
            <description><![CDATA[<p>AI is all around. Can I use it for enhancing home automation scenarios?</p>
<p>Few days ago OpenAI released the GPT-4 vision capabilities on their API, so I thought on using AI and GPT4 vision models to analyze images of a security camera connected to a well-known home automation system: Home Assistant.</p>
<p>The goal was to trigger a notification to my mobile, if the system detects a potential security issue at home using AI :-)</p>
<p>Possible? Perhaps! Let's see!</p>
<h2 id="quick-overview">Quick overview</h2>
<p>Let me start by introducing some concepts.</p>
<h3 id="about-home-assistant">About Home Assistant</h3>
<p><a href="https://www.home-assistant.io/">Home Assistant</a> is a well-known open-source home automation project that enables easy implementation of domotics / home automation systems.
It is a very flexible tool that can be extended in order to provide further functionalities.
In HASS, we have:</p>
<ul>
<li>devices</li>
<li>entities, usually provided from deviced</li>
<li>automations, made of "triggers", "conditions" and "actions"</li>
<li>services</li>
<li>... and much more!</li>
</ul>
<h3 id="about-the-openai-conversation-integration">About the OpenAI Conversation integration</h3>
<p>Home Assistant provides an <a href="https://www.home-assistant.io/integrations/openai_conversation">integration with OpenAI</a> that can:</p>
<ul>
<li>enable an assistant using GPT-3 language model, so that users can use the Assist feature to interact and, for example, get information about their devices and sensors using a natural language interface (e.g., english)</li>
<li>generate images based on a prompt</li>
</ul>
<p>This integration works but it doesn't provide image analysis though.</p>
<h2 id="initial-challenges">Initial challenges</h2>
<p>I had a look at the OpenAI Conversation integration provided in Home Assistant, dive into the code and found out that it was using an old version of the OpenAI python library; I tried to bump it to the latest version but the API changed a lot so adapting the existing code was not going to be an easy task to me.</p>
<p>I also don't know much the Home Assistant codebase and Python is not something I use on a daily basis.</p>
<p>However, these challenges wouldn't stop me.</p>
<h2 id="how-i-made-it">How I made it</h2>
<h3 id="quick-oveview">Quick oveview</h3>
<p>I started by creating a copy of the existing code and tried to "hack it" locally, to a local directory <code>/custom_components/openai_conversation_custom </code>. I had the official integration already installed which somehow conflicted with this code.
I then,</p>
<ul>
<li>removed the official integration from Home Assistant</li>
<li>changed the <code>manifest.json</code></li>
</ul>
<div class="remark-highlight"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
 <span class="token property">"domain"</span><span class="token operator">:</span> <span class="token string">"openai_conversation_custom"</span><span class="token punctuation">,</span>
 <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"OpenAI Conversation Custom"</span><span class="token punctuation">,</span>
 <span class="token property">"codeowners"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"@balloob"</span><span class="token punctuation">,</span> <span class="token string">"@bitcoder"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
 <span class="token property">"config_flow"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
 <span class="token property">"dependencies"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"conversation"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
 <span class="token property">"documentation"</span><span class="token operator">:</span> <span class="token string">"https://www.home-assistant.io/integrations/openai_conversation"</span><span class="token punctuation">,</span>
 <span class="token property">"integration_type"</span><span class="token operator">:</span> <span class="token string">"service"</span><span class="token punctuation">,</span>
 <span class="token property">"iot_class"</span><span class="token operator">:</span> <span class="token string">"cloud_polling"</span><span class="token punctuation">,</span>
 <span class="token property">"requirements"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"openai==0.27.2"</span><span class="token punctuation">,</span> <span class="token string">"httpx"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
 <span class="token property">"version"</span><span class="token operator">:</span> <span class="token string">"1.0.0-custom-20231124"</span>
<span class="token punctuation">}</span>
</code></pre></div>
<ul>
<li>did changes on the code to add additional features (more info ahead)</li>
<li>installed the custom integration</li>
</ul>
<p><img src="https://www.sergiofreire.com/assets/blog/home-automation-with-ai-vision/openai_custom_config.jpg" alt="OpenAI Conversation Custom integration configuration" loading="lazy"></p>
<h3 id="the-code-i-added-to-support-new-features">The code I added to support new features</h3>
<p>The code that I'm about to share is not totally clean and it's like putting a parallel OpenAI client to support my needs, that include the image processing.
Please consider it as a proof-of-concept and adapt it to your needs.</p>
<p>I created my OpenAI client inspired by Piotr Skalski and added it under the <code>myopenai_client</code> directory.
The code uses the `httpx`` library for making asynchronous HTTP requests.
Let me share some brief snippets of it.</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">import</span> os
<span class="token keyword">import</span> numpy <span class="token keyword">as</span> np
<span class="token keyword">import</span> httpx

<span class="token keyword">from</span> <span class="token punctuation">.</span>utils <span class="token keyword">import</span> compose_payload<span class="token punctuation">,</span> compose_text_payload


<span class="token keyword">class</span> <span class="token class-name">OpenAIConnector</span><span class="token punctuation">:</span>

    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> api_key<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">if</span> api_key <span class="token keyword">is</span> <span class="token boolean">None</span><span class="token punctuation">:</span>
            <span class="token keyword">raise</span> ValueError<span class="token punctuation">(</span><span class="token string">"API_KEY is not set"</span><span class="token punctuation">)</span>
        self<span class="token punctuation">.</span>api_key <span class="token operator">=</span> api_key

    <span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">simple_prompt</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> prompt<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> image_path<span class="token punctuation">:</span> <span class="token builtin">str</span> <span class="token operator">=</span> <span class="token boolean">None</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">str</span><span class="token punctuation">:</span>
        headers <span class="token operator">=</span> <span class="token punctuation">{</span>
            <span class="token string">"Content-Type"</span><span class="token punctuation">:</span> <span class="token string">"application/json"</span><span class="token punctuation">,</span>
            <span class="token string">"Authorization"</span><span class="token punctuation">:</span> <span class="token string-interpolation"><span class="token string">f"Bearer </span><span class="token interpolation"><span class="token punctuation">{</span>self<span class="token punctuation">.</span>api_key<span class="token punctuation">}</span></span><span class="token string">"</span></span>
        <span class="token punctuation">}</span>
        <span class="token keyword">if</span> image_path <span class="token keyword">is</span> <span class="token boolean">None</span><span class="token punctuation">:</span>
            payload <span class="token operator">=</span> compose_text_payload<span class="token punctuation">(</span>prompt<span class="token operator">=</span>prompt<span class="token punctuation">)</span>
        <span class="token keyword">else</span><span class="token punctuation">:</span>
            payload <span class="token operator">=</span> compose_payload<span class="token punctuation">(</span>image_path<span class="token operator">=</span>image_path<span class="token punctuation">,</span> prompt<span class="token operator">=</span>prompt<span class="token punctuation">)</span>

        <span class="token keyword">async</span> <span class="token keyword">with</span> httpx<span class="token punctuation">.</span>AsyncClient<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> client<span class="token punctuation">:</span>
            response <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">await</span> client<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string">"https://api.openai.com/v1/chat/completions"</span><span class="token punctuation">,</span>
                                         headers<span class="token operator">=</span>headers<span class="token punctuation">,</span> json<span class="token operator">=</span>payload<span class="token punctuation">,</span> timeout<span class="token operator">=</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span>

        <span class="token keyword">return</span> response<span class="token punctuation">[</span><span class="token string">'choices'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'message'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'content'</span><span class="token punctuation">]</span>
</code></pre></div>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">compose_payload</span><span class="token punctuation">(</span>image_path<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> prompt<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">dict</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""</span>
<span class="token triple-quoted-string string">    Composes a payload dictionary with a base64 encoded image and a text prompt for the GPT-4 Vision model.</span>
<span class="token triple-quoted-string string"></span>
<span class="token triple-quoted-string string">    Args:</span>
<span class="token triple-quoted-string string">        image_path (str): The image path to encode and include in the payload.</span>
<span class="token triple-quoted-string string">        prompt (str): The prompt text to accompany the image in the payload.</span>
<span class="token triple-quoted-string string"></span>
<span class="token triple-quoted-string string">    Returns:</span>
<span class="token triple-quoted-string string">        dict: A dictionary structured as a payload for the GPT-4 Vision model, including the model name,</span>
<span class="token triple-quoted-string string">              an array of messages each containing a role and content with text and the base64 encoded image,</span>
<span class="token triple-quoted-string string">              and the maximum number of tokens to generate.</span>
<span class="token triple-quoted-string string">    """</span>

    <span class="token comment"># encode a base64 image from the image path</span>
    <span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span>image_path<span class="token punctuation">,</span> <span class="token string">"rb"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> image_file<span class="token punctuation">:</span>
         encoded_string <span class="token operator">=</span> base64<span class="token punctuation">.</span>b64encode<span class="token punctuation">(</span>image_file<span class="token punctuation">.</span>read<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
         base64_image <span class="token operator">=</span> encoded_string<span class="token punctuation">.</span>decode<span class="token punctuation">(</span><span class="token string">'utf-8'</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
        <span class="token string">"model"</span><span class="token punctuation">:</span> <span class="token string">"gpt-4-vision-preview"</span><span class="token punctuation">,</span>
        <span class="token string">"messages"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
                <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
                    <span class="token punctuation">{</span>
                        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"text"</span><span class="token punctuation">,</span>
                        <span class="token string">"text"</span><span class="token punctuation">:</span> prompt
                    <span class="token punctuation">}</span><span class="token punctuation">,</span>
                    <span class="token punctuation">{</span>
                        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"image_url"</span><span class="token punctuation">,</span>
                        <span class="token string">"image_url"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                            <span class="token string">"url"</span><span class="token punctuation">:</span> <span class="token string-interpolation"><span class="token string">f"data:image/jpeg;base64,</span><span class="token interpolation"><span class="token punctuation">{</span>base64_image<span class="token punctuation">}</span></span><span class="token string">"</span></span>
                        <span class="token punctuation">}</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">]</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token string">"max_tokens"</span><span class="token punctuation">:</span> <span class="token number">600</span>
    <span class="token punctuation">}</span>

<span class="token keyword">def</span> <span class="token function">compose_text_payload</span><span class="token punctuation">(</span>prompt<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">dict</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""</span>
<span class="token triple-quoted-string string">    Composes a payload dictionary with a base64 encoded image and a text prompt the standard text model.</span>
<span class="token triple-quoted-string string"></span>
<span class="token triple-quoted-string string">    Args:</span>
<span class="token triple-quoted-string string">        prompt (str): The prompt text to accompany the image in the payload.</span>
<span class="token triple-quoted-string string"></span>
<span class="token triple-quoted-string string">    Returns:</span>
<span class="token triple-quoted-string string">        dict: A dictionary structured as a payload for the GPT-4 Vision model, including the model name,</span>
<span class="token triple-quoted-string string">              an array of messages each containing a role and content with text and the base64 encoded image,</span>
<span class="token triple-quoted-string string">              and the maximum number of tokens to generate.</span>
<span class="token triple-quoted-string string">    """</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
        <span class="token string">"model"</span><span class="token punctuation">:</span> <span class="token string">"gpt-4-vision-preview"</span><span class="token punctuation">,</span>
        <span class="token string">"messages"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
                <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
                    <span class="token punctuation">{</span>
                        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"text"</span><span class="token punctuation">,</span>
                        <span class="token string">"text"</span><span class="token punctuation">:</span> prompt
                    <span class="token punctuation">}</span>
                <span class="token punctuation">]</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token string">"max_tokens"</span><span class="token punctuation">:</span> <span class="token number">600</span>
    <span class="token punctuation">}</span>
</code></pre></div>
<p>The idea is to provide the possibility of asking something to OpenAI GPT-4 vision model, passing a question (i.e., the prompt) together with the image.</p>
<p>This was encapsulated in a new service. Therefore, on the <code>__init__.py</code> this new service that I called <code>process_image</code>:</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">async_setup</span><span class="token punctuation">(</span>hass<span class="token punctuation">:</span> HomeAssistant<span class="token punctuation">,</span> config<span class="token punctuation">:</span> ConfigType<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">bool</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Set up OpenAI Conversation."""</span>

    <span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">process_image</span><span class="token punctuation">(</span>call<span class="token punctuation">:</span> ServiceCall<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> ServiceResponse<span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Process an image..."""</span>
        api_key <span class="token operator">=</span> hass<span class="token punctuation">.</span>data<span class="token punctuation">[</span>DOMAIN<span class="token punctuation">]</span><span class="token punctuation">[</span>call<span class="token punctuation">.</span>data<span class="token punctuation">[</span><span class="token string">"config_entry"</span><span class="token punctuation">]</span><span class="token punctuation">]</span>
        client <span class="token operator">=</span> OpenAIConnector<span class="token punctuation">(</span>api_key<span class="token operator">=</span>api_key<span class="token punctuation">)</span>
        prompt <span class="token operator">=</span> call<span class="token punctuation">.</span>data<span class="token punctuation">[</span><span class="token string">"prompt"</span><span class="token punctuation">]</span>
        image_path <span class="token operator">=</span> call<span class="token punctuation">.</span>data<span class="token punctuation">[</span><span class="token string">"file_path"</span><span class="token punctuation">]</span>
        response <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span>simple_prompt<span class="token punctuation">(</span>prompt<span class="token operator">=</span>prompt<span class="token punctuation">,</span> image_path<span class="token operator">=</span>image_path<span class="token punctuation">)</span>

        <span class="token keyword">return</span> <span class="token punctuation">{</span>
            <span class="token string">"response"</span><span class="token punctuation">:</span> response
        <span class="token punctuation">}</span>

    hass<span class="token punctuation">.</span>services<span class="token punctuation">.</span>async_register<span class="token punctuation">(</span>
        DOMAIN<span class="token punctuation">,</span>
        SERVICE_PROCESS_IMAGE<span class="token punctuation">,</span>
        process_image<span class="token punctuation">,</span>
        schema<span class="token operator">=</span>vol<span class="token punctuation">.</span>Schema<span class="token punctuation">(</span>
            <span class="token punctuation">{</span>
                vol<span class="token punctuation">.</span>Required<span class="token punctuation">(</span><span class="token string">"config_entry"</span><span class="token punctuation">)</span><span class="token punctuation">:</span> selector<span class="token punctuation">.</span>ConfigEntrySelector<span class="token punctuation">(</span>
                    <span class="token punctuation">{</span>
                        <span class="token string">"integration"</span><span class="token punctuation">:</span> DOMAIN<span class="token punctuation">,</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">)</span><span class="token punctuation">,</span>
                vol<span class="token punctuation">.</span>Required<span class="token punctuation">(</span><span class="token string">"prompt"</span><span class="token punctuation">)</span><span class="token punctuation">:</span> cv<span class="token punctuation">.</span>string<span class="token punctuation">,</span>
                vol<span class="token punctuation">.</span>Required<span class="token punctuation">(</span><span class="token string">"file_path"</span><span class="token punctuation">)</span><span class="token punctuation">:</span> cv<span class="token punctuation">.</span>string<span class="token punctuation">,</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">)</span><span class="token punctuation">,</span>
        supports_response<span class="token operator">=</span>SupportsResponse<span class="token punctuation">.</span>ONLY<span class="token punctuation">,</span>
    <span class="token punctuation">)</span>
 
    <span class="token keyword">return</span> <span class="token boolean">True</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
</code></pre></div>
<p>On the <code>services.yaml</code> I added the description of the configuration for this new service, so that it can be used in an automation action later on.</p>
<div class="remark-highlight"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">process_image</span><span class="token punctuation">:</span>
  <span class="token key atrule">fields</span><span class="token punctuation">:</span>
    <span class="token key atrule">config_entry</span><span class="token punctuation">:</span>
      <span class="token key atrule">required</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
      <span class="token key atrule">selector</span><span class="token punctuation">:</span>
        <span class="token key atrule">config_entry</span><span class="token punctuation">:</span>
          <span class="token key atrule">integration</span><span class="token punctuation">:</span> openai_conversation_custom
    <span class="token key atrule">prompt</span><span class="token punctuation">:</span>
      <span class="token key atrule">required</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
      <span class="token key atrule">selector</span><span class="token punctuation">:</span>
        <span class="token key atrule">text</span><span class="token punctuation">:</span>
          <span class="token key atrule">multiline</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
    <span class="token key atrule">file_path</span><span class="token punctuation">:</span>
      <span class="token key atrule">required</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
      <span class="token key atrule">selector</span><span class="token punctuation">:</span>
        <span class="token key atrule">text</span><span class="token punctuation">:</span>
          <span class="token key atrule">multiline</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>
</code></pre></div>
<p>I ended up having the previous functionality provided by the original OpenAI Conversation integration using its own API client together with a parallel client to support this new service. Not very clean, I know, but it works for my PoC.</p>
<h2 id="implementation">Implementation</h2>
<p>After having the custom integration installed and configured with the OpenAI API key, I created a few things in my Home Automation.</p>
<p>These were the main automation rules I created.</p>
<ul>
<li>Automation rule: take a screenshot whenever the camera detects a movement, and then analyze it using AI</li>
</ul>
<p><img src="https://www.sergiofreire.com/assets/blog/home-automation-with-ai-vision/analyze_rule.jpg" alt="Analyze image with AI automation rule" loading="lazy"></p>
<p>In this rule I'll use the new service that I created earlier. I have to pass a prompt. I'm using the following ones. Note that making a good prompt is essential; you can read a bit more on Prompt Engineering and some good practices.</p>
<div class="remark-highlight"><pre class="language-unknown"><code class="language-unknown">As a home security expert looking at the photo of a camera, do you see any security related issues that are signs of vandalism, robbery, broken items, open windows, fire, smoke, or other conditions that can put the house and its inhabitants at risk? Please answer with NORMAL or WARNING or CRITICAL in the first line, by increasing risk level respectively, and then please describe the main risks you have identified.</code></pre></div>
<ul>
<li>Automation rule: notify my mobile when the image analysis changes to a certain state (e.g., "CRITICAL" or "WARNING)</li>
</ul>
<p><img src="https://www.sergiofreire.com/assets/blog/home-automation-with-ai-vision/notify_due_to_image_analysis.jpg" alt="Analyze image with AI automation rule" loading="lazy"></p>
<p>In terms of dashboard and entities, I added a few things.</p>
<p>To see the photo that the camera took, I used the <code>local_file</code> camera platform. This allowed to me overcome some issues I faced with the picture entity that wasn't able to refresh the image upon new snapshots being taken.
I also created a template based sensor entity to store the status of the image analysis. That way I can also show it on the dashboard.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/home-automation-with-ai-vision/dashboard_config.jpg" alt="dashboard live" loading="lazy"></p>
<h2 id="seeing-it-working">Seeing it working</h2>
<p>After having my Home Assistant configuration worked out, I simulated someone entering the room.
This was my dashboard showed.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/home-automation-with-ai-vision/dashboard_upon_detection.jpg" alt="dashboard upon detection" loading="lazy"></p>
<p>This is the mobile notification I got.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/home-automation-with-ai-vision/mobile_notification.jpg" alt="mobile notification upon detection" loading="lazy"></p>
<p>Cool, isnt' it?</p>
<h2 id="things-to-have-in-mind">Things to have in mind</h2>
<p>There are a few things worth mentioning:</p>
<ul>
<li>please test the prompt you use to analyze the images; the quality of results will be highly dependent on that</li>
<li>don't trust 100% on the image analysis; you can get false positives and false negatives</li>
<li>test, a lot :)</li>
<li>and remember, that this is just a PoC!</li>
</ul>
<h2 id="in-sum">In sum</h2>
<p>This small project was challenging but also fun.
It shows how AI, in this case using OpenAI vision and the power of GPT-4, can be use to augment some of the the things we do, such as being used to analyze images from security cameras.</p>
<p>It's not a foolproof solution, but it can be used together with other data to provide some automation in your existing home automation projects!
If you want to try this yourself, and since you got to this point on the tutorial, please check out the <a href="https://github.com/bitcoder/openai_conversation_custom">GitHub repository</a> I put online.</p>
<p>Have fun!</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/home-automation-with-ai-vision/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Exploratory Testing Principles 1.0]]></title>
            <link>https://www.sergiofreire.com/post/exploratory-testing-principles</link>
            <guid>https://www.sergiofreire.com/post/exploratory-testing-principles</guid>
            <pubDate>Fri, 27 Jan 2023 12:30:00 GMT</pubDate>
            <description><![CDATA[<p>This is v1.0 of my Exploratory Testing Principles. But what is a principle?</p>
<blockquote>
<p>Principle: A belief, a basic truth from where other truths can be built</p>
</blockquote>
<p>Whenever testing, we also have a set of principles that guide and support our testing.</p>
<p>Let's see the principles I think might be important.</p>
<h2 id="exploratory-testing-principles-v10">Exploratory Testing Principles v1.0</h2>
<h3 id="1-testing-is-about-searching-for-meaningful-quality-related-information">1. Testing is about searching for meaningful quality related information</h3>
<p>Testing provides information about quality which in turn has multiple dimensions and multiple stakeholders that value those dimensions (quality criteria) differently. The perceived value on each quality dimension also evolves with time...</p>
<h3 id="2-testing-is-highly-context-dependent-and-context-driven">2. Testing is highly context-dependent and context-driven</h3>
<p>The risks that we want to assess and how we perform that assessment depend on the context. Likewise, results of testing are contextualized and need to be interpreted.</p>
<h3 id="3-testing-embraces-a-whole-set-of-activities-to-challenge-investigate-explore-check-report-and-provide-actionable-information">3. Testing embraces a whole set of activities, to challenge, investigate, explore, check, report and provide actionable information</h3>
<p>Although the essence of testing is around finding problems through investigation, exploration, and checking, testing also involves planning, reporting, and other activities.</p>
<h3 id="4-testing-is-mainly-a-human-centered-process-that-can-be-augmented-by-tools-to-an-extent">4. Testing is mainly a human centered process that can be augmented by tools to an extent</h3>
<p>Testing is mainly driven by knowledge, curiosity, and context; these are some main tools used by a tester. Software and hardware tools can also be used to assist testers and augment certain testing activities and may provide information that otherwise would be hard or impossible to obtain.</p>
<h3 id="5-there-is-no-single-testing-solution">5. There is no single testing solution</h3>
<p>Similarly to coding where there is no single implementation for a given feature, there is also no single testing solution to uncover risks and the risks that matter. However, there are techniques, heuristics, and approaches that maximize results towards specific testing goals.</p>
<h3 id="6-testing-uncovers-more-risks-whenever-performed-with-someone-else">6. Testing uncovers more risks whenever performed with someone else</h3>
<p>Testing can be a “lone ranger” activity but we know that diversity plays a major role around improving knowledge and understanding. Each person has a different background, is aware of different heuristics and risks, and has a different skill set. Testing with someone else, including developers, is a proven-way of exposing risks that otherwise could be missed. Besides, if done with developers for example, it’s a way of improving testing knowledge so that quality can be built-in in the future.</p>
<h3 id="7-testing-requires-extensive-skills-technical-social-among-others">7. Testing requires extensive skills, technical, social, among others</h3>
<p>These skills need to evolve, as context evolves, including the software, how it is crafted, how it is delivered and used. The art of crafting value through software is a team effort, thus social and communication skills are also important to foster an overall team improvement environment.</p>
<h3 id="8-testing-is-not-limited-to-certain-roles-stages-types-levels-or-artifacts">8. Testing is not limited to certain roles, stages, types, levels, or artifacts</h3>
<p>Everyone in the team can contribute to testing to have better and timed information about quality, so that risks can be addressed properly and promptly. Testing can and should be applied to all aspects that ultimately lead to a feature in production, in other words, all aspects from ideation up to production.</p>
<h3 id="9-testing-is-never-complete">9. Testing is never complete</h3>
<p>As there are numerous stakeholders (internal and external) interested in our products, that in turn have different expectations and use the product differently, the number of test scenarios that we could depict would be infinite. Similarly to coding where we can always refactor code snippets, testing is never complete as we can always learn new information about the software, its internals and its externals. The more time and resources we have, the more we can test. However, there is always a decision of when to stop testing that can be around the confidence the team has so far or restricted by some time constraint, for example.</p>
<h3 id="10-there-are-no-bug-free-products">10. There are no bug free products</h3>
<p>Bug free products are a myth. We can only say that we have not found bugs during and for the testing that we performed. Bugs are all aspects that negatively impact quality. Even though we cannot assure a product is bug free, it’s always possible to assess risks and mitigate them through testing, among other options that the team can take.</p>
<h3 id="11-testing-is-maximized-with-clear-meaningful-and-fast-feedback-loops">11. Testing is maximized with clear, meaningful, and fast feedback-loops</h3>
<p>Information discovered while testing needs to be shared with the team, so the team can act on it. This information needs to be understandable, manageable, and meaningful. In order for the team to improve, this information needs to be properly delivered and on-time. Enabling fast feedback loops is essential to iterate more often and thus enable agility.</p>
<h2 id="in-sum">In sum</h2>
<p>This is a first iteration of the principles I think may relevant for testers, including exploratory testers; in reality, being exploratory is part of the intrinsic nature of testers.
Looking forward for your feedback in order to improve these principles or can even create your own variant of your testing principles.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/exploratory-testing-principles/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Exporting test runs to InfluxDB and Grafana]]></title>
            <link>https://www.sergiofreire.com/post/exporting-testruns-to-grafana</link>
            <guid>https://www.sergiofreire.com/post/exporting-testruns-to-grafana</guid>
            <pubDate>Thu, 08 Sep 2022 18:00:00 GMT</pubDate>
            <description><![CDATA[<p>After doing a <a href="/post/exporting-testruns-to-splunk">similar exercise with Splunk</a>, I decided to give a try to Grafana along with InfluxDB. The idea was to use Grafana to analyze and visualize test results, in this case from a well-known test management solution, <a href="https://getxray.app">Xray</a>, where I work btw.</p>
<p>I've met Grafana in the past but never implemented an end-to-end scenario with it. Well, it was time; hope this helps you out and inspires you to try some of these tools. In this rather long detailed article, we'll also cover InfluxDB and how it can be useful to store time series data.</p>
<h2 id="background">Background</h2>
<h3 id="xray">Xray</h3>
<p>Xray is a well-known test management solution that works highly integrated with Jira (it needs it, actually).
Xray cloud (i.e., for Jira cloud) provides a REST API and a <a href="https://docs.getxray.app/display/XRAYCLOUD/GraphQL+API">GraphQL API</a>. The latter is more powerfull and that's the one we can use to extract the test runs.</p>
<p>The Xray server/datacenter product provides a REST API only, for the time being. In this case, there's an endpoint that we can use to export the test runs from.</p>
<h3 id="influxdb">InfluxDB</h3>
<p><a href="https://www.influxdata.com/products/influxdb-overview/">InfluxDB</a> is a time series database. It can be used to store and query our time-related data: in this case our test runs. It also provides integration with Grafana among other.</p>
<h3 id="grafana">Grafana</h3>
<p><a href="https://grafana.com/grafana/">Grafana</a> is a platform used to analyze and visualize data, supporting multiple sources, dashboard customization, plugins, and more.</p>
<h2 id="how-to">How to</h2>
<p>I started by trying to use Grafana directly to import the test runs from the source application (i.e., Xray), using either the GraphQL API, for Xray cloud, or the REST API, for Xray server/DC.</p>
<p>It's possible to do so using some Grafana plugins (GraphQL, JSON API, Infinity) but we will face many limitations, including:</p>
<ul>
<li>unable to deal with pagination of the underlying APIs;</li>
<li>overload of API request, as data needs to be fetched each time it is needed</li>
<li>limited query capabilities</li>
</ul>
<p>These are just a few; I won't cover the configuration of these or explore this further to not make this article too exhaustive.</p>
<p>So, as this initial approach was unsuccessful, I tried to depict what would be the proper architecture for this. As a consequence, I came with something like this: <strong>Xray > InfluxDB > Grafana</strong>.</p>
<h3 id="extracting-test-runs-from-xray">Extracting test runs from Xray</h3>
<p>The first step is to extract data from Xray.
Here, we actually have two different product flavors: one for server/datacenter and another cloud based. The underlying APIs are different, so I had to make two different implementations; in your case, you'll probably just need one of them.</p>
<h4 id="extracting-test-runs-from-xray-cloud">Extracting test runs from Xray cloud</h4>
<p>To achieve this, I've implemented some code in JavaScript.
The code ahead uses a <a href="https://www.atlassian.com/blog/jira-software/jql-the-most-flexible-way-to-search-jira-14">JQL expression</a> to define the source of the data; in this case, I aim to export all test runs from project "CALC" in Jira, based on the related Test Execution issues. We need to obtain the related issue ids (Jira internal ids) of these, as the <a href="https://xray.cloud.getxray.app/doc/graphql/gettestruns.doc.html"><code>getTestRuns</code></a> GraphQL function requires us to pass them (or the Test issue ids, if we prefer to obtain test runs based on that).</p>
<p>The code also has some basic logic to deal with pagination on the GraphQL requests; more error handling should be provided btw as, for example, the API can return temporary errors or rate limiting errors.</p>
<p>There's also logic to export test runs based on a modification date, and use that to avoid exporting them on next export operations.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">var</span> axios <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'axios'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">GraphQLClient</span><span class="token punctuation">,</span> gql <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'graphql-request'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> xray_cloud_base_url <span class="token operator">=</span> <span class="token string">"https://xray.cloud.getxray.app/api/v2"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> xray_cloud_graphql_url <span class="token operator">=</span> xray_cloud_base_url <span class="token operator">+</span> <span class="token string">"/graphql"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> client_id <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">CLIENT_ID</span> <span class="token operator">||</span> <span class="token string">"215FFD69FE4644728C72182E00000000"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> client_secret <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">CLIENT_SECRET</span> <span class="token operator">||</span> <span class="token string">"1c00f8f22f56a8684d7c18cd6147ce2787d95e4da9f3bfb0af8f02ec00000000"</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> authenticate_url <span class="token operator">=</span> xray_cloud_base_url <span class="token operator">+</span> <span class="token string">"/authenticate"</span><span class="token punctuation">;</span>

<span class="token comment">// Jira JQL query to define the list of Test Execution issues to export test runs from</span>
jql <span class="token operator">=</span> <span class="token string">"project=CALC and issuetype = 'Test Execution'"</span>


<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getTestExecutionIds</span> <span class="token punctuation">(</span><span class="token parameter">jql<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">return</span> axios<span class="token punctuation">.</span><span class="token method function property-access">post</span><span class="token punctuation">(</span>authenticate_url<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string-property property">"client_id"</span><span class="token operator">:</span> client_id<span class="token punctuation">,</span> <span class="token string-property property">"client_secret"</span><span class="token operator">:</span> client_secret <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
    
    <span class="token keyword">var</span> auth_token <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> graphQLClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GraphQLClient</span><span class="token punctuation">(</span>xray_cloud_graphql_url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
        <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>auth_token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>

      <span class="token comment">// console.log(auth_token);</span>

      <span class="token keyword">const</span> testexec_ids_query <span class="token operator">=</span> gql<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token graphql language-graphql"> 
      <span class="token keyword">query</span>
      <span class="token punctuation">{</span>
          <span class="token property-query">getTestExecutions</span><span class="token punctuation">(</span><span class="token attr-name">jql</span><span class="token punctuation">:</span> <span class="token string">"<span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>jql<span class="token interpolation-punctuation punctuation">}</span></span>"</span><span class="token punctuation">,</span> <span class="token attr-name">limit</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>limit<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">,</span> <span class="token attr-name">start</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>start<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token object">results</span><span class="token punctuation">{</span>
              <span class="token property">issueId</span>
            <span class="token punctuation">}</span>
          <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
</span><span class="token template-punctuation string">`</span></span>

    <span class="token keyword control-flow">return</span> graphQLClient<span class="token punctuation">.</span><span class="token method function property-access">request</span><span class="token punctuation">(</span>testexec_ids_query<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      testexec_ids <span class="token operator">=</span> data<span class="token punctuation">[</span><span class="token string">'getTestExecutions'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'results'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">t</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token keyword control-flow">return</span> t<span class="token punctuation">[</span><span class="token string">'issueId'</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token comment">// console.log(testexec_ids);</span>
      <span class="token keyword control-flow">return</span> testexec_ids<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error performing query to obtain Test Execution ids: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error on Authentication: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>


<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getTestRuns</span> <span class="token punctuation">(</span><span class="token parameter">testExecIssueIds<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit<span class="token punctuation">,</span> modifiedSince</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">return</span> axios<span class="token punctuation">.</span><span class="token method function property-access">post</span><span class="token punctuation">(</span>authenticate_url<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string-property property">"client_id"</span><span class="token operator">:</span> client_id<span class="token punctuation">,</span> <span class="token string-property property">"client_secret"</span><span class="token operator">:</span> client_secret <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>

      <span class="token keyword">var</span> auth_token <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> graphQLClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GraphQLClient</span><span class="token punctuation">(</span>xray_cloud_graphql_url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
            <span class="token literal-property property">authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>auth_token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
          <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>

        testexec_ids <span class="token operator">=</span> testExecIssueIds<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">t</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
          <span class="token keyword control-flow">return</span> <span class="token string">'"'</span> <span class="token operator">+</span> t <span class="token operator">+</span> <span class="token string">'"'</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// console.log(testexec_ids);</span>

        <span class="token keyword">const</span> query <span class="token operator">=</span> gql<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token graphql language-graphql"> 
        <span class="token punctuation">{</span>
          <span class="token property-query">getTestRuns</span><span class="token punctuation">(</span><span class="token attr-name">testExecIssueIds</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>testexec_ids<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token attr-name">limit</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>limit<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">,</span> <span class="token attr-name">start</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>start<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">,</span> <span class="token attr-name">modifiedSince</span><span class="token punctuation">:</span> <span class="token string">"<span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>modifiedSince<span class="token interpolation-punctuation punctuation">}</span></span>"</span>  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token property">total</span>
            <span class="token property">start</span>
        
            <span class="token object">results</span><span class="token punctuation">{</span>
              <span class="token property">id</span>
              <span class="token object">status</span><span class="token punctuation">{</span>
                <span class="token property">name</span>
                <span class="token property">description</span>
              <span class="token punctuation">}</span>
              <span class="token property">comment</span>
              <span class="token object">evidence</span><span class="token punctuation">{</span>
                <span class="token property">filename</span>
                <span class="token property">downloadLink</span>
              <span class="token punctuation">}</span>
              <span class="token property">defects</span>
              <span class="token property">executedById</span>
              <span class="token property">startedOn</span>
              <span class="token property">finishedOn</span>
              <span class="token property">assigneeId</span>
        
              <span class="token object">testType</span><span class="token punctuation">{</span>
                <span class="token property">name</span>
              <span class="token punctuation">}</span>
        
              <span class="token object">steps</span> <span class="token punctuation">{</span>
                  <span class="token property">id</span>
                  <span class="token property">action</span>
                  <span class="token property">data</span>
                  <span class="token property">result</span>
                  <span class="token object">customFields</span> <span class="token punctuation">{</span>
                    <span class="token property">name</span>
                    <span class="token property">value</span>
                  <span class="token punctuation">}</span>
                  <span class="token property">comment</span>
                  <span class="token object">evidence</span><span class="token punctuation">{</span>
                    <span class="token property">filename</span>
                    <span class="token property">downloadLink</span>
                  <span class="token punctuation">}</span>
                  <span class="token object">attachments</span> <span class="token punctuation">{</span>
                      <span class="token property">id</span>
                      <span class="token property">filename</span>
                  <span class="token punctuation">}</span>
                  <span class="token property">defects</span>
                  <span class="token property">actualResult</span>
                  <span class="token object">status</span> <span class="token punctuation">{</span>
                    <span class="token property">name</span>
                  <span class="token punctuation">}</span>
              <span class="token punctuation">}</span>
        
              <span class="token property">scenarioType</span>
              <span class="token property">gherkin</span>
              <span class="token object">examples</span> <span class="token punctuation">{</span>
                  <span class="token property">id</span>
                  <span class="token object">status</span> <span class="token punctuation">{</span>
                      <span class="token property">name</span>
                      <span class="token property">description</span>
                  <span class="token punctuation">}</span>
                  <span class="token property">duration</span>
              <span class="token punctuation">}</span>
        
              <span class="token property">unstructured</span>
              
              <span class="token object">customFields</span> <span class="token punctuation">{</span>
                  <span class="token property">id</span>
                  <span class="token property">name</span>
                  <span class="token property">values</span>
              <span class="token punctuation">}</span>
        
              <span class="token property-query">preconditions</span><span class="token punctuation">(</span><span class="token attr-name">limit</span><span class="token punctuation">:</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token object">results</span><span class="token punctuation">{</span>
                    <span class="token object">preconditionRef</span> <span class="token punctuation">{</span>
                        <span class="token property">issueId</span>
                        <span class="token property-query">jira</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
                    <span class="token punctuation">}</span>
                    <span class="token property">definition</span>
                <span class="token punctuation">}</span>
              <span class="token punctuation">}</span>
              <span class="token object">test</span> <span class="token punctuation">{</span>
                  <span class="token property">issueId</span>
                  <span class="token property-query">jira</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
              <span class="token punctuation">}</span>
              <span class="token object">testExecution</span> <span class="token punctuation">{</span>
                  <span class="token property">issueId</span>
                  <span class="token property-query">jira</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">,</span> <span class="token string">"fixVersions"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
                  <span class="token property">testEnvironments</span>
                  <span class="token property-query">testPlans</span><span class="token punctuation">(</span><span class="token attr-name">start</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token attr-name">limit</span><span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
										<span class="token object">results</span><span class="token punctuation">{</span>
											 <span class="token property-query">jira</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
										<span class="token punctuation">}</span>
									<span class="token punctuation">}</span>
              <span class="token punctuation">}</span>    
            <span class="token punctuation">}</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>  
  </span><span class="token template-punctuation string">`</span></span>

      <span class="token keyword control-flow">return</span> graphQLClient<span class="token punctuation">.</span><span class="token method function property-access">request</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword control-flow">return</span> data<span class="token punctuation">[</span><span class="token string">'getTestRuns'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'results'</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error performing query to obtain testruns: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error on Authentication: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token doc-comment comment">/**** main *****/</span>

<span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>

  <span class="token keyword">let</span> configFile <span class="token operator">=</span> <span class="token string">'export_testruns.json'</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fs<span class="token punctuation">.</span><span class="token method function property-access">existsSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    fs<span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">,</span> <span class="token string">"{}"</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">let</span> config <span class="token operator">=</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token method function property-access">readFileSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> modifiedSince <span class="token operator">=</span> config<span class="token punctuation">[</span><span class="token string">'modifiedSince'</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token string">"2021-01-01T00:00:00Z"</span>

  <span class="token comment">// obtain Test Execution issue ids</span>
  <span class="token keyword">let</span> start <span class="token operator">=</span> <span class="token number">0</span>
  <span class="token keyword">let</span> limit <span class="token operator">=</span> <span class="token number">100</span>
  <span class="token keyword">let</span> testexecs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword">let</span> tes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword control-flow">do</span> <span class="token punctuation">{</span>
    tes <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getTestExecutionIds</span><span class="token punctuation">(</span>jql<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit<span class="token punctuation">)</span>
    start <span class="token operator">+=</span> limit
    testexecs<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token spread operator">...</span>tes<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>tes<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>

  <span class="token comment">// obtain the Test Runs for the given Test Execution issue ids, modified since a given data</span>
  <span class="token keyword">let</span> testruns <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  start <span class="token operator">=</span> <span class="token number">0</span>
  <span class="token keyword">let</span> trs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword control-flow">do</span> <span class="token punctuation">{</span>
    trs <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getTestRuns</span><span class="token punctuation">(</span>testexecs<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit<span class="token punctuation">,</span> modifiedSince<span class="token punctuation">)</span>
    start <span class="token operator">+=</span> limit
    testruns<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token spread operator">...</span>trs<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>trs<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>
  
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>testruns<span class="token punctuation">,</span> <span class="token keyword nil">undefined</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  fs<span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span><span class="token string">'testruns.json'</span><span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>testruns<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  config<span class="token punctuation">[</span><span class="token string">'modifiedSince'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">split</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">+</span><span class="token string">"Z"</span>
  fs<span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre></div>
<p>The previous script will generate a <code>testruns.json</code> file, having an array of JSON objects, each one corresponding to a test run.</p>
<h4 id="extracting-test-runs-from-xray-serverdatacenter">Extracting test runs from Xray server/datacenter</h4>
<p>To achieve this, once again I made some code in JavaScript that uses the <a href="https://docs.getxray.app/display/XRAY/Export+Execution+Results+-+REST">Xray server/DC REST API endpoint for exporting test runs</a>.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">var</span> axios <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'axios'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> create <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'domain'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> jira_base_url <span class="token operator">=</span> <span class="token string">"https://local_jiraserver.local"</span><span class="token punctuation">;</span>
<span class="token comment">//var personal_access_token = "OTE0ODc2NDE2NTgxOnrhigwOreFoyNIA9lXTZaOcgbNY";</span>
<span class="token keyword">var</span> jira_username <span class="token operator">=</span> <span class="token string">'someuser'</span>
<span class="token keyword">var</span> jira_password <span class="token operator">=</span> <span class="token string">'somepass'</span>


<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">createJiraFilter</span><span class="token punctuation">(</span><span class="token parameter">jql</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">var</span> basicAuth <span class="token operator">=</span> <span class="token string">'Basic '</span> <span class="token operator">+</span> <span class="token function">btoa</span><span class="token punctuation">(</span>jira_username <span class="token operator">+</span> <span class="token string">':'</span> <span class="token operator">+</span> jira_password<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">let</span> url <span class="token operator">=</span> jira_base_url <span class="token operator">+</span> <span class="token string">"/rest/api/2/filter"</span>
  <span class="token keyword">const</span> filter <span class="token operator">=</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>
      <span class="token punctuation">{</span>
        <span class="token string-property property">"jql"</span><span class="token operator">:</span> jql<span class="token punctuation">,</span>
        <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"temporary filter"</span><span class="token punctuation">,</span>
        <span class="token string-property property">"description"</span><span class="token operator">:</span> <span class="token string">"temporary filter to indirectly export test runs from"</span><span class="token punctuation">,</span>
        <span class="token string-property property">"favourite"</span><span class="token operator">:</span> <span class="token boolean">false</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword control-flow">return</span> axios<span class="token punctuation">.</span><span class="token method function property-access">post</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> filter<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'Authorization'</span><span class="token operator">:</span> basicAuth<span class="token punctuation">,</span> <span class="token string-property property">'Content-Type'</span><span class="token operator">:</span> <span class="token string">'application/json'</span> <span class="token punctuation">}</span>
    <span class="token comment">// headers: { 'Authorization': "Bearer " + personal_access_token }</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'success: created temporary filter '</span> <span class="token operator">+</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token string">' in Jira'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword control-flow">return</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error creating filter: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">deleteJiraFilter</span><span class="token punctuation">(</span><span class="token parameter">filterId</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">var</span> basicAuth <span class="token operator">=</span> <span class="token string">'Basic '</span> <span class="token operator">+</span> <span class="token function">btoa</span><span class="token punctuation">(</span>jira_username <span class="token operator">+</span> <span class="token string">':'</span> <span class="token operator">+</span> jira_password<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">let</span> url <span class="token operator">=</span> jira_base_url <span class="token operator">+</span> <span class="token string">"/rest/api/2/filter/"</span> <span class="token operator">+</span> filterId
  <span class="token keyword control-flow">return</span> axios<span class="token punctuation">.</span><span class="token method function property-access">delete</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'Authorization'</span><span class="token operator">:</span> basicAuth <span class="token punctuation">}</span>
    <span class="token comment">// headers: { 'Authorization': "Bearer " + personal_access_token }</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'success: created temporary filter '</span> <span class="token operator">+</span> filterId <span class="token operator">+</span> <span class="token string">' in Jira'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword control-flow">return</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error deleting filter: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>


<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getTestRuns</span><span class="token punctuation">(</span><span class="token parameter">filterId<span class="token punctuation">,</span> page<span class="token punctuation">,</span> limit</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">var</span> basicAuth <span class="token operator">=</span> <span class="token string">'Basic '</span> <span class="token operator">+</span> <span class="token function">btoa</span><span class="token punctuation">(</span>jira_username <span class="token operator">+</span> <span class="token string">':'</span> <span class="token operator">+</span> jira_password<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">var</span> endpoint_url <span class="token operator">=</span> jira_base_url <span class="token operator">+</span> <span class="token string">"/rest/raven/2.0/testruns"</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> params <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">savedFilterId</span><span class="token operator">:</span> filterId<span class="token punctuation">,</span>
    <span class="token literal-property property">includeTestFields</span><span class="token operator">:</span> <span class="token string">"issuelinks"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">page</span><span class="token operator">:</span> page<span class="token punctuation">,</span>
    <span class="token literal-property property">limit</span><span class="token operator">:</span> limit
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> url <span class="token operator">=</span> endpoint_url <span class="token operator">+</span> <span class="token string">"?"</span> <span class="token operator">+</span> params<span class="token punctuation">;</span>

  <span class="token keyword control-flow">return</span> axios<span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'Authorization'</span><span class="token operator">:</span> basicAuth <span class="token punctuation">}</span>
      <span class="token comment">// headers: { 'Authorization': "Bearer " + personal_access_token }</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// console.log('success');</span>
      <span class="token comment">// console.log(response.data);</span>
      <span class="token keyword control-flow">return</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error exporting test runs: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>


<span class="token doc-comment comment">/**** main *****/</span>


<span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>


  <span class="token comment">/*</span>
<span class="token comment">    We can either use an existing Jira filter, by its id, or create a temporary one so we can define</span>
<span class="token comment">     the JQL expression in the code. </span>
<span class="token comment">    If we choose the latter, i.e., to create a temporary filter, then we should cleanup at the end of the process.</span>
<span class="token comment">  */</span>

  <span class="token comment">// Jira JQL expression to indirectly obtain the test runs from</span>
  jql <span class="token operator">=</span> <span class="token string">"project = BOOK and issuetype = 'Test Execution'"</span>

  <span class="token comment">// create a temporary filter based on a JQL expression</span>
  <span class="token keyword">let</span> filter <span class="token operator">=</span>  <span class="token keyword control-flow">await</span> <span class="token function">createJiraFilter</span><span class="token punctuation">(</span>jql<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> filterId <span class="token operator">=</span> filter<span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span>

  <span class="token comment">// obtain the Test Runs for the given Jira filter id</span>
  <span class="token keyword">let</span> testruns <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword">let</span> page <span class="token operator">=</span> <span class="token number">1</span>
  <span class="token keyword">let</span> limit <span class="token operator">=</span> <span class="token number">100</span>
  <span class="token keyword">let</span> trs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword control-flow">do</span> <span class="token punctuation">{</span>
    trs <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getTestRuns</span><span class="token punctuation">(</span>filterId<span class="token punctuation">,</span> page<span class="token punctuation">,</span> limit<span class="token punctuation">)</span>
    page <span class="token operator">+=</span> <span class="token number">1</span>
    testruns<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token spread operator">...</span>trs<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>trs<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>
  

  <span class="token comment">// delete temporary filter, if we created it </span>
  <span class="token keyword control-flow">await</span> <span class="token function">deleteJiraFilter</span><span class="token punctuation">(</span>filterId<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>testruns<span class="token punctuation">,</span> <span class="token keyword nil">undefined</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  fs<span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span><span class="token string">'testruns_dc.json'</span><span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>testruns<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div>
<p>This will generate a JSON file <code>testruns_dc.json</code> containing an array of Test Runs.</p>
<h3 id="importing-test-runs-to-influxdb">Importing test runs to InfluxDB</h3>
<p>After we have the test runs in a local file, now we need to import it to our time series database: InfluxDB.</p>
<p>In InfluxDB we have a <a href="https://docs.influxdata.com/influxdb/cloud/reference/key-concepts/">bunch of concepts</a>; a careful reading should be done before proceeding with an actual implementation.</p>
<ul>
<li><strong>bucket</strong>:	All InfluxDB data is stored in a bucket. A bucket combines the concept of a database and a retention period</li>
<li><strong>measurement</strong>:A measurement acts as a container for tags, fields, and timestamps. Use a measurement name that describes your data.</li>
<li><strong>timestamp</strong>: All data stored in InfluxDB has a _time column that stores timestamps</li>
<li>point: A point includes the series key, a field (key and value), and a timestamp (and optionally tags).</li>
<li><strong>series</strong>: A series key is a collection of points that share a measurement, tag set, and field key.</li>
<li><strong>field</strong>: A field includes a field key stored in the _field column and a field value stored in the _value column.</li>
<li><strong>tags</strong>: Tags include tag keys and tag values that are stored as strings and metadata; it's like an index</li>
</ul>
<p>Therefore, we start by creating a bucket (like a database); we can do this from InfluxDB UI. I've named it "xray".</p>
<p>Then we need to think about naming the measurement; but what is exactly a measurement? That's an excellent question. We can think of a measurement like a big table of data. It can be used as an abstraction of a specific measurement (e.g., temperature); it's basically a name that describes our data. I used our test runs as an abstraction of the target of our measurement, and thus I named it "testrun".</p>
<p>Next, we need to think about how to map our test run details. We have fields and tags... so what we should them for?</p>
<p>The following code snippet shows the approach that I've followed, where fields are used to store dynamic information and tags are used for more static kinds of data.
We have to be <a href="https://docs.influxdata.com/influxdb/cloud/write-data/best-practices/resolve-high-cardinality/">careful with series cardinality</a> or else we'll hit some limits and cause performance issues; see <a href="https://docs.influxdata.com/influxdb/cloud/write-data/best-practices/schema-design/">best practices for schema design</a>.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> point <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Point</span><span class="token punctuation">(</span><span class="token string">'testrun'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">timestamp</span><span class="token punctuation">(</span>ts<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'deployment_type'</span><span class="token punctuation">,</span> <span class="token string">'cloud'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'iter'</span><span class="token punctuation">,</span> iter<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'test_type'</span><span class="token punctuation">,</span> test_type<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'status'</span><span class="token punctuation">,</span> status<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'test_key'</span><span class="token punctuation">,</span> test_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testexec_key'</span><span class="token punctuation">,</span> testexec_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testplan_key'</span><span class="token punctuation">,</span> testplan_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'test_environments'</span><span class="token punctuation">,</span> test_environments<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'version'</span><span class="token punctuation">,</span> version<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'revision'</span><span class="token punctuation">,</span> revision<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testrun_id'</span><span class="token punctuation">,</span> testrun_id<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">floatField</span><span class="token punctuation">(</span><span class="token string">'duration'</span><span class="token punctuation">,</span> duration<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">intField</span><span class="token punctuation">(</span><span class="token string">'total_defects'</span><span class="token punctuation">,</span> total_defects<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">intField</span><span class="token punctuation">(</span><span class="token string">'total_evidence'</span><span class="token punctuation">,</span> total_evidence<span class="token punctuation">)</span>
</code></pre></div>
<p>I've used a tag for deployment type (e.g., "cloud" or "server"), another for an iteration identifier to ease my tests, another for the test type, and finally one for the test run status/result.</p>
<p>I've used one of the <a href="https://github.com/influxdata/influxdb-client-js/tree/master/examples">InfluxDB client examples</a> and adapted it to my needs. The API is straightforward.</p>
<p>There's a small thing to have in mind: the previous JSON files I obtained for Xray server/DC and cloud don't follow exactly the same format. So, I'll need to normalize that whenever I upload these to InfluxDB.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token hashbang comment">#!/usr/bin/env node</span>

<span class="token keyword module">import</span> <span class="token punctuation">{</span><span class="token maybe-class-name">InfluxDB</span><span class="token punctuation">,</span> <span class="token maybe-class-name">Point</span><span class="token punctuation">,</span> <span class="token known-class-name class-name">HttpError</span><span class="token punctuation">}</span> <span class="token keyword module">from</span> <span class="token string">'@influxdata/influxdb-client'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span>url<span class="token punctuation">,</span> token<span class="token punctuation">,</span> org<span class="token punctuation">,</span> bucket<span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'./env.mjs'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span>hostname<span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'node:os'</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token operator">*</span> <span class="token keyword module">as</span> fs</span> <span class="token keyword module">from</span> <span class="token string">'fs'</span><span class="token punctuation">;</span>


<span class="token keyword">function</span> <span class="token function">normalizeStatus</span><span class="token punctuation">(</span><span class="token parameter">status</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
  <span class="token keyword">var</span> statuses <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
  statuses<span class="token punctuation">[</span><span class="token string">'PASSED'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'PASS'</span><span class="token punctuation">;</span>
  statuses<span class="token punctuation">[</span><span class="token string">'FAILED'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'FAIL'</span><span class="token punctuation">;</span>
  statuses<span class="token punctuation">[</span><span class="token string">'TO DO'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'TODO'</span><span class="token punctuation">;</span>

  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>statuses<span class="token punctuation">[</span>status<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">return</span> statuses<span class="token punctuation">[</span>status<span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">return</span> status<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'*** WRITE POINTS ***'</span><span class="token punctuation">)</span>
<span class="token comment">// create a write API, expecting point timestamps in nanoseconds (can be also 's', 'ms', 'us')</span>
<span class="token keyword">const</span> writeApi <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InfluxDB</span><span class="token punctuation">(</span><span class="token punctuation">{</span>url<span class="token punctuation">,</span> token<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">getWriteApi</span><span class="token punctuation">(</span>org<span class="token punctuation">,</span> bucket<span class="token punctuation">,</span> <span class="token string">'ns'</span><span class="token punctuation">)</span>
<span class="token comment">// setup default tags for all writes through this API</span>
<span class="token comment">// writeApi.useDefaultTags({location: hostname()})</span>

<span class="token comment">// a tag to help us diagnose the results</span>
<span class="token keyword">let</span> iter <span class="token operator">=</span> <span class="token string">"i6"</span><span class="token punctuation">;</span>

<span class="token comment">// Revision custom field</span>
<span class="token keyword">let</span> revision_cf <span class="token operator">=</span> <span class="token string">"customfield_10028"</span><span class="token punctuation">;</span>

<span class="token comment">// testEnvironments: dc, cloud via test execution</span>
<span class="token comment">// fixversion: dc (no!), cloud via testexecution</span>
<span class="token comment">// revision: dc (no!), cloud via testexecution andd custom field id (e.g., adding "customfield_xxxx" to the "jira" element)</span>
<span class="token comment">// testplan key: dc (no!), cloud via test execution</span>
<span class="token comment">// import Xray server/DC testruns</span>
<span class="token keyword">let</span> testruns <span class="token operator">=</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span> fs<span class="token punctuation">.</span><span class="token method function property-access">readFileSync</span><span class="token punctuation">(</span><span class="token string">'./testruns_dc.json'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&#x3C;</span> testruns<span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> ts <span class="token operator">=</span>  <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"start"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword nil">undefined</span><span class="token punctuation">)</span> <span class="token operator">&#x26;&#x26;</span> <span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"finish"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword null nil">null</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    ts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"start"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">let</span> duration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"finish"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword nil">undefined</span><span class="token punctuation">)</span> <span class="token operator">&#x26;&#x26;</span> <span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"finish"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword null nil">null</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    duration <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token known-class-name class-name">Date</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"finish"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">-</span> ts<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">1000.0</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">let</span> testrun_id <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> test_key <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testKey"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> testexec_key <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testExecKey"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> test_type <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"type"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> status <span class="token operator">=</span> <span class="token function">normalizeStatus</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"status"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> total_defects <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"defects"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> total_evidence <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"evidences"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> point <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Point</span><span class="token punctuation">(</span><span class="token string">'testrun'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">timestamp</span><span class="token punctuation">(</span>ts<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'deployment_type'</span><span class="token punctuation">,</span> <span class="token string">'server'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'iter'</span><span class="token punctuation">,</span> iter<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'test_key'</span><span class="token punctuation">,</span> test_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testexec_key'</span><span class="token punctuation">,</span> testexec_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'test_type'</span><span class="token punctuation">,</span> test_type<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'status'</span><span class="token punctuation">,</span> status<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testrun_id'</span><span class="token punctuation">,</span> testrun_id<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">floatField</span><span class="token punctuation">(</span><span class="token string">'duration'</span><span class="token punctuation">,</span> duration<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">intField</span><span class="token punctuation">(</span><span class="token string">'total_defects'</span><span class="token punctuation">,</span> total_defects<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">intField</span><span class="token punctuation">(</span><span class="token string">'total_evidence'</span><span class="token punctuation">,</span> total_evidence<span class="token punctuation">)</span>

  writeApi<span class="token punctuation">.</span><span class="token method function property-access">writePoint</span><span class="token punctuation">(</span>point<span class="token punctuation">)</span>  
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>point<span class="token punctuation">.</span><span class="token method function property-access">toLineProtocol</span><span class="token punctuation">(</span>writeApi<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>


<span class="token comment">// import Xray cloud testruns</span>
testruns <span class="token operator">=</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span> fs<span class="token punctuation">.</span><span class="token method function property-access">readFileSync</span><span class="token punctuation">(</span><span class="token string">'./testruns.json'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&#x3C;</span> testruns<span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> ts <span class="token operator">=</span>  <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"startedOn"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword nil">undefined</span><span class="token punctuation">)</span> <span class="token operator">&#x26;&#x26;</span> <span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"startedOn"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword null nil">null</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    ts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"startedOn"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">let</span> duration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"finishedOn"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword nil">undefined</span><span class="token punctuation">)</span> <span class="token operator">&#x26;&#x26;</span> <span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"finishedOn"</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token keyword null nil">null</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    duration <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token known-class-name class-name">Date</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"finishedOn"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">-</span> ts<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">1000.0</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">let</span> testrun_id <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> test_key <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"test"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"jira"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> testexec_key <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testExecution"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"jira"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> testplan_key <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testExecution"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testPlans"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"results"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">tp</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">return</span> tp<span class="token punctuation">[</span><span class="token string">"jira"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> test_environments <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testExecution"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testEnvironments"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> version <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testExecution"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"jira"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"fixVersions"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">version</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword control-flow">return</span> version<span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> revision <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testExecution"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"jira"</span><span class="token punctuation">]</span><span class="token punctuation">[</span>revision_cf<span class="token punctuation">]</span><span class="token punctuation">;</span>

  <span class="token keyword">let</span> test_type <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"testType"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> status <span class="token operator">=</span> <span class="token function">normalizeStatus</span><span class="token punctuation">(</span>testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"status"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"name"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> total_defects <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"defects"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> total_evidence <span class="token operator">=</span> testruns<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">"evidence"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> point <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Point</span><span class="token punctuation">(</span><span class="token string">'testrun'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">timestamp</span><span class="token punctuation">(</span>ts<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'deployment_type'</span><span class="token punctuation">,</span> <span class="token string">'cloud'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'iter'</span><span class="token punctuation">,</span> iter<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'test_key'</span><span class="token punctuation">,</span> test_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testexec_key'</span><span class="token punctuation">,</span> testexec_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testplan_key'</span><span class="token punctuation">,</span> testplan_key<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'test_environments'</span><span class="token punctuation">,</span> test_environments<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'version'</span><span class="token punctuation">,</span> version<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'revision'</span><span class="token punctuation">,</span> revision<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'test_type'</span><span class="token punctuation">,</span> test_type<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">tag</span><span class="token punctuation">(</span><span class="token string">'status'</span><span class="token punctuation">,</span> status<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">stringField</span><span class="token punctuation">(</span><span class="token string">'testrun_id'</span><span class="token punctuation">,</span> testrun_id<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">floatField</span><span class="token punctuation">(</span><span class="token string">'duration'</span><span class="token punctuation">,</span> duration<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">intField</span><span class="token punctuation">(</span><span class="token string">'total_defects'</span><span class="token punctuation">,</span> total_defects<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">intField</span><span class="token punctuation">(</span><span class="token string">'total_evidence'</span><span class="token punctuation">,</span> total_evidence<span class="token punctuation">)</span>
  writeApi<span class="token punctuation">.</span><span class="token method function property-access">writePoint</span><span class="token punctuation">(</span>point<span class="token punctuation">)</span>  
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>point<span class="token punctuation">.</span><span class="token method function property-access">toLineProtocol</span><span class="token punctuation">(</span>writeApi<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>


<span class="token comment">// WriteApi always buffer data into batches to optimize data transfer to InfluxDB server.</span>
<span class="token comment">// writeApi.flush() can be called to flush the buffered data. The data is always written</span>
<span class="token comment">// asynchronously, Moreover, a failed write (caused by a temporary networking or server failure)</span>
<span class="token comment">// is retried automatically. Read `writeAdvanced.js` for better explanation and details.</span>
<span class="token comment">//</span>
<span class="token comment">// close() flushes the remaining buffered data and then cancels pending retries.</span>
<span class="token keyword control-flow">try</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">await</span> writeApi<span class="token punctuation">.</span><span class="token method function property-access">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'FINISHED!'</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span> <span class="token keyword control-flow">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">error</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">HttpError</span> <span class="token operator">&#x26;&#x26;</span> e<span class="token punctuation">.</span><span class="token property-access">statusCode</span> <span class="token operator">===</span> <span class="token number">401</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Try to setup a new InfluxDB database.'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'\nFinished with ERROR'</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div>
<p>After uploading the data, we can query it using <a href="https://docs.influxdata.com/flux/v0.x/">Flux</a>.</p>
<p>A Flux script/query starts by defining the source bucket using <code>from(bucket: "somebucket")</code>, then an absolute/relative time range using <code>range()</code>, followed by one or more filters using <code>filter(fn: (r) =>  ... )</code>.</p>
<p>After data is retrieved it can be transformed (e.g., grouped, manipulated). I would recommend having a look at <a href="https://awesome.influxdata.com/docs/part-2/querying-and-data-transformations/">Time to Awesome online book</a>.</p>
<p>A simple Flux query example follows.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"somebucket"</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> <span class="token operator">-</span>5d<span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span> <span class="token operator">-</span>1d<span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"field1"</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span>columns<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"tag1"</span><span class="token punctuation">,</span> <span class="token string">"tag2"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
</code></pre></div>
<p>We'll see ahead some concrete Flux examples.</p>
<h3 id="configuring-and-using-grafana-to-analyze-and-visualize-the-test-runs">Configuring and using Grafana to analyze and visualize the test runs</h3>
<p>In Grafana, we need to configure a data source.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/grafana_influxdb_datasource1.jpg" alt="Configuring InfluxDB datasource in Grafana - part 1" loading="lazy"></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/grafana_influxdb_datasource2.jpg" alt="Configuring InfluxDB datasource in Grafana - part 2" loading="lazy"></p>
<p>Then we can create a dashboard and add multiple panels to visualize the information we need.</p>
<p>We may find it useful to define some variables that apply across all panels and related queries that we'll use on our dashboard. These can be used later on using the <code>${variable_name}</code> syntax.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/dashboard_variables.jpg" alt="Configuring variables in a Grafana dashboard" loading="lazy"></p>
<h4 id="total-test-runs-for-the-selected-timeframe">Total Test Runs, for the selected timeframe</h4>
<p>In this example, we need to start by joining the multiple tables from the input stream. For that we use the <code>group()</code> statement; we may specify the column it will use, which by default will be the <code>_value</code> column. We can then use the <code>count()</code> function.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> and
    r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"duration"</span> 
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"${iter}"</span>
  <span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">count</span><span class="token punctuation">(</span>column<span class="token operator">:</span> <span class="token string">"_value"</span><span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/total_testruns_counter.jpg" alt="Total Test Runs counter, for the selected timeframe" loading="lazy"></p>
<h4 id="total-defects-for-the-selected-timeframe">Total Defects, for the selected timeframe</h4>
<p>Here we use the <code>sum()</code> aggregation function.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> and
    r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"total_defects"</span> 
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"${iter}"</span>
  <span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">sum</span><span class="token punctuation">(</span>column<span class="token operator">:</span> <span class="token string">"_value"</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/total_defects_counter.jpg" alt="Total defects counter, for the selected timeframe" loading="lazy"></p>
<h4 id="number-of-test-runs-grouped-in-2-months-blocks-for-the-selected-timeframe">Number of Test Runs, grouped in 2 months blocks, for the selected timeframe</h4>
<p>In this example, we use the <code>aggregateWindow()</code> function which is a nice way to perform a calculus for a certain time window. The following snippet shows doing a count for every 2 months window. Note that we have to do a <code>group()</code> before to join the multiple input data tables.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> and
    r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"duration"</span> 
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"${iter}"</span>
  <span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">aggregateWindow</span><span class="token punctuation">(</span>every<span class="token operator">:</span> 2mo<span class="token punctuation">,</span> <span class="token literal-property property">fn</span><span class="token operator">:</span> count<span class="token punctuation">,</span> <span class="token literal-property property">createEmpty</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/testruns_count_grouped_2m.jpg" alt="Number of Test Runs, grouped in 2 months blocks, for the selected timeframe" loading="lazy"></p>
<h4 id="number-of-defects-reported-on-test-runs-grouped-in-3-months-blocks-for-the-selected-timeframe">Number of defects reported on Test Runs, grouped in 3 months blocks, for the selected timeframe</h4>
<p>In this case, we use the <code>aggregateWindow()</code> together with the <code>sum</code> aggregation function.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> and
    r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"total_defects"</span> 
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"${iter}"</span>
  <span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">aggregateWindow</span><span class="token punctuation">(</span>every<span class="token operator">:</span> 3mo<span class="token punctuation">,</span> <span class="token literal-property property">fn</span><span class="token operator">:</span> sum<span class="token punctuation">,</span> <span class="token literal-property property">createEmpty</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/defects_count_grouped_3m.jpg" alt="Number of defects reported on Test Runs, grouped in 3 months blocks, for the selected timeframe" loading="lazy"></p>
<h4 id="table-with-top-3-test-runs-by-duration-for-the-selected-timeframe">Table with top 3 Test Runs by duration, for the selected timeframe</h4>
<p>Here I use the <code>top()</code> function to obtain 3 rows of data (i.e., 3 test runs) based on the <code>duration</code> field. There's a nice trick here: the <code>schema.fieldsAsCols()</code> function will map all the fields to columns, named by the respective field key.</p>
<p>I also have created a new column "url", using the Flux <code>map()</code> function, that creates the URL based on two other columns that contain the Test and the Test Execution issue keys. I've considered a typical Xray cloud link for seeing the test run details.</p>
<p>Then I added an override to format the "url" field as a link, using a "data link" with the value <code>${__value.text}</code>.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">import</span> <span class="token string">"influxdata/influxdb/schema"</span>

<span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> <span class="token function">and</span>
    <span class="token punctuation">(</span>r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"duration"</span>  or r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"testrun_id"</span> or r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"test_key"</span> or r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"testexec_key"</span><span class="token punctuation">)</span>
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"$iter"</span> 
  <span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> schema<span class="token punctuation">.</span><span class="token method function property-access">fieldsAsCols</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">keep</span><span class="token punctuation">(</span>columns<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"_time"</span><span class="token punctuation">,</span> <span class="token string">"testrun_id"</span><span class="token punctuation">,</span> <span class="token string">"test_key"</span><span class="token punctuation">,</span> <span class="token string">"testexec_key"</span><span class="token punctuation">,</span> <span class="token string">"test_type"</span><span class="token punctuation">,</span> <span class="token string">"duration"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">top</span><span class="token punctuation">(</span>n<span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token literal-property property">columns</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"duration"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">map</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>r <span class="token keyword">with</span> <span class="token literal-property property">url</span><span class="token operator">:</span>  <span class="token string">"https://sergiofreire.atlassian.net/plugins/servlet/ac/com.xpandit.plugins.xray/execution-page?ac.testExecIssueKey=${r.testexec_key}&#x26;ac.testIssueKey=${r.test_key}"</span>  
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/table_top3_trs_duration.jpg" alt="Table with top 3 Test Runs by duration, for the selected timeframe" loading="lazy"></p>
<h4 id="table-with-the-3-most-recent-test-runs-having-defects-for-the-selected-timeframe">Table with the 3 most recent Test Runs having defects, for the selected timeframe</h4>
<p>In this case, mostly similar to the previous one, I've used two <code>filter()</code> statements, where the last one filters test runs that just have reported defects.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">import</span> <span class="token string">"influxdata/influxdb/schema"</span>

<span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> <span class="token function">and</span>
    <span class="token punctuation">(</span>r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"duration"</span>  or r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"testrun_id"</span> or r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"test_key"</span> or r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"testexec_key"</span> or r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"total_defects"</span><span class="token punctuation">)</span>
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"$iter"</span> 
  <span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> schema<span class="token punctuation">.</span><span class="token method function property-access">fieldsAsCols</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">keep</span><span class="token punctuation">(</span>columns<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"_time"</span><span class="token punctuation">,</span> <span class="token string">"testrun_id"</span><span class="token punctuation">,</span> <span class="token string">"test_key"</span><span class="token punctuation">,</span> <span class="token string">"testexec_key"</span><span class="token punctuation">,</span> <span class="token string">"test_type"</span><span class="token punctuation">,</span> <span class="token string">"total_defects"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
  r<span class="token punctuation">.</span><span class="token property-access">total_defects</span> <span class="token operator">></span> <span class="token number">0</span> 
<span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">top</span><span class="token punctuation">(</span>n<span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token literal-property property">columns</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"_time"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token operator">|</span><span class="token operator">></span> <span class="token function">map</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>r <span class="token keyword">with</span> <span class="token literal-property property">url</span><span class="token operator">:</span>  <span class="token string">"https://sergiofreire.atlassian.net/plugins/servlet/ac/com.xpandit.plugins.xray/execution-page?ac.testExecIssueKey=${r.testexec_key}&#x26;ac.testIssueKey=${r.test_key}"</span>  
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/table_recent_trs_defects.jpg" alt="Table with the 3 most recent Test Runs having defects, for the selected timeframe" loading="lazy"></p>
<h4 id="test-runs-counters-by-status">Test Runs counters by status</h4>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> and
    r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"duration"</span> 
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"${iter}"</span>
  <span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span>columns<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"status"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">count</span><span class="token punctuation">(</span>column<span class="token operator">:</span> <span class="token string">"_value"</span><span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/testruns_counters_status.jpg" alt="Test Runs counters by status" loading="lazy"></p>
<h4 id="test-run-duration-mean-grouped-by-test-type">Test Run duration (mean) grouped by test type</h4>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword module">from</span><span class="token punctuation">(</span>bucket<span class="token operator">:</span> <span class="token string">"xray"</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">range</span><span class="token punctuation">(</span>start<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token property-access">timeRangeStart</span><span class="token punctuation">,</span> <span class="token literal-property property">stop</span><span class="token operator">:</span>v<span class="token punctuation">.</span><span class="token property-access">timeRangeStop</span><span class="token punctuation">)</span>
  <span class="token operator">|</span><span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span>
    r<span class="token punctuation">.</span><span class="token property-access">_measurement</span> <span class="token operator">==</span> <span class="token string">"testrun"</span> and
    r<span class="token punctuation">.</span><span class="token property-access">_field</span> <span class="token operator">==</span> <span class="token string">"duration"</span> 
    and r<span class="token punctuation">.</span><span class="token property-access">iter</span> <span class="token operator">==</span> <span class="token string">"${iter}"</span>
  <span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">group</span><span class="token punctuation">(</span>columns<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"test_type"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
 <span class="token operator">|</span><span class="token operator">></span> <span class="token function">aggregateWindow</span><span class="token punctuation">(</span>every<span class="token operator">:</span> 2mo<span class="token punctuation">,</span> <span class="token literal-property property">fn</span><span class="token operator">:</span> mean<span class="token punctuation">,</span> <span class="token literal-property property">createEmpty</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/testruns_duration_test_type.jpg" alt="Test Run duration (mean) grouped by test type" loading="lazy"></p>
<h2 id="final-considerations">Final considerations</h2>
<p>Overall, this was a very interesting exercise.</p>
<p>Extracting data from my source tool (i.e. Xray) was the easiest part, honestly.</p>
<p>I struggled more with InfluxDB kind of concepts; I'm glad I had a call with their team, which proactively reached out and helped clarify some basics.
The biggest challenge for me was moving from a SQL mindset to a time series database, where data is returned as a stream of tables, for example.</p>
<p>Grafana is a very powerful tool, and its integration with InfluxDB seems a good fit.
Sometimes I had some doubts with renaming columns or grouping data, if I should do it directly at the InfluxDB level (i.e., on the query), or by using the transformations that Grafana provides.</p>
<p>In the end I was able to implement a bunch of charts and listings with information that could be useful from a testing perspective.</p>
<p>I really think that these are powerful tools that can probably address many reporting kinds of needs teams may have. Some of the team I worked with, used Grafana for different purposes (e.g., testing, infrastructure/monitoring metrics).</p>
<p>Maybe these can also be useful to you, who knows?</p>
<h2 id="useful-references">Useful references</h2>
<ul>
<li><a href="https://docs.getxray.app/display/XRAYCLOUD/GraphQL+API">Xray cloud GraphQL API</a></li>
<li><a href="https://docs.getxray.app/display/XRAY/Export+Execution+Results+-+REST">Xray server/DC REST API endpoint for exporting test runs</a></li>
<li><a href="https://grafana.com/grafana/">Grafana</a></li>
<li><a href="https://docs.influxdata.com/influxdb/cloud/xx">InfluxDB cloud documentation</a></li>
<li><a href="https://docs.influxdata.com/flux/v0.x/">Flux scripting language</a></li>
<li><a href="https://awesome.influxdata.com/">Time to Awesome, a book on InfluxDB</a></li>
</ul>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-grafana/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Exporting test runs to Splunk]]></title>
            <link>https://www.sergiofreire.com/post/exporting-testruns-to-splunk</link>
            <guid>https://www.sergiofreire.com/post/exporting-testruns-to-splunk</guid>
            <pubDate>Thu, 01 Sep 2022 12:00:00 GMT</pubDate>
            <description><![CDATA[<p>Experimentation is part of the learning process. Quite often I'm challenged with trying out new tools, integrate with them, learn from them. I do like challenges. This week someone asked about exporting information (i.e., test runs) from a well-known test management solution, <a href="https://getxray.app">Xray</a>, where I work btw, to Splunk.
I was intrigued by this; first, I know Splunk for years but never used it in real projects, at least that I remember :) second, I didn't understand the exact value offer nor if it was feasible!</p>
<p>This post I about sharing my learnings and how I got it working.</p>
<h2 id="background">Background</h2>
<h3 id="xray">Xray</h3>
<p>Xray is a well-known test management solution that works highly integrated with Jira (it needs it, actually).
Xray cloud (i.e., for Jira cloud) provides a REST API and a <a href="https://docs.getxray.app/display/XRAYCLOUD/GraphQL+API">GraphQL API</a>. The latter is more powerfull and that's the one we can use to extract the test runs.</p>
<h3 id="splunk">Splunk</h3>
<p><a href="https://www.splunk.com/en_us/products/splunk-cloud-platform.html">Splunk Cloud Platform</a> is a data oriented platform used to search, visualize, and process data from multiple sources, providing analytics and observability kind of capabilities.</p>
<h2 id="how-to">How to</h2>
<p>The first step is to extract data from Xray cloud. To achieve this, I've implemented some code in JavaScript.
The code ahead uses a <a href="https://www.atlassian.com/blog/jira-software/jql-the-most-flexible-way-to-search-jira-14">JQL expression</a> to define the source of the data; in this case, I aim to export all test runs from project "CALC" in Jira, based on the related Test Execution issues. We need to obtain the related issue ids (Jira internal ids) of these, as the <a href="https://xray.cloud.getxray.app/doc/graphql/gettestruns.doc.html"><code>getTestRuns</code></a> GraphQL function requires us to pass them (or the Test issue ids, if we prefer to obtain test runs based on that).</p>
<p>The code also has some basic logic to deal with pagination on the GraphQL requests; more error handling should be provided btw as, for example, the API can return temporary errors or rate limiting errors.</p>
<p>There's also logic to export test runs based on a modification date, and use that to avoid exporting them on next export operations.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">var</span> axios <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'axios'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">GraphQLClient</span><span class="token punctuation">,</span> gql <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'graphql-request'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> xray_cloud_base_url <span class="token operator">=</span> <span class="token string">"https://xray.cloud.getxray.app/api/v2"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> xray_cloud_graphql_url <span class="token operator">=</span> xray_cloud_base_url <span class="token operator">+</span> <span class="token string">"/graphql"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> client_id <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">CLIENT_ID</span> <span class="token operator">||</span> <span class="token string">"215FFD69FE4644728C72182E00000000"</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> client_secret <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">CLIENT_SECRET</span> <span class="token operator">||</span> <span class="token string">"1c00f8f22f56a8684d7c18cd6147ce2787d95e4da9f3bfb0af8f02ec00000000"</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> authenticate_url <span class="token operator">=</span> xray_cloud_base_url <span class="token operator">+</span> <span class="token string">"/authenticate"</span><span class="token punctuation">;</span>

<span class="token comment">// Jira JQL query to define the list of Test Execution issues to export test runs from</span>
jql <span class="token operator">=</span> <span class="token string">"project=CALC and issuetype = 'Test Execution'"</span>


<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getTestExecutionIds</span> <span class="token punctuation">(</span><span class="token parameter">jql<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">return</span> axios<span class="token punctuation">.</span><span class="token method function property-access">post</span><span class="token punctuation">(</span>authenticate_url<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string-property property">"client_id"</span><span class="token operator">:</span> client_id<span class="token punctuation">,</span> <span class="token string-property property">"client_secret"</span><span class="token operator">:</span> client_secret <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
    
    <span class="token keyword">var</span> auth_token <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> graphQLClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GraphQLClient</span><span class="token punctuation">(</span>xray_cloud_graphql_url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
        <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>auth_token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span>

      <span class="token comment">// console.log(auth_token);</span>

      <span class="token keyword">const</span> testexec_ids_query <span class="token operator">=</span> gql<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token graphql language-graphql"> 
      <span class="token keyword">query</span>
      <span class="token punctuation">{</span>
          <span class="token property-query">getTestExecutions</span><span class="token punctuation">(</span><span class="token attr-name">jql</span><span class="token punctuation">:</span> <span class="token string">"<span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>jql<span class="token interpolation-punctuation punctuation">}</span></span>"</span><span class="token punctuation">,</span> <span class="token attr-name">limit</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>limit<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">,</span> <span class="token attr-name">start</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>start<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token object">results</span><span class="token punctuation">{</span>
              <span class="token property">issueId</span>
            <span class="token punctuation">}</span>
          <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
</span><span class="token template-punctuation string">`</span></span>

    <span class="token keyword control-flow">return</span> graphQLClient<span class="token punctuation">.</span><span class="token method function property-access">request</span><span class="token punctuation">(</span>testexec_ids_query<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      testexec_ids <span class="token operator">=</span> data<span class="token punctuation">[</span><span class="token string">'getTestExecutions'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'results'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">t</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token keyword control-flow">return</span> t<span class="token punctuation">[</span><span class="token string">'issueId'</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token comment">// console.log(testexec_ids);</span>
      <span class="token keyword control-flow">return</span> testexec_ids<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error performing query to obtain Test Execution ids: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error on Authentication: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>


<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getTestRuns</span> <span class="token punctuation">(</span><span class="token parameter">testExecIssueIds<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit<span class="token punctuation">,</span> modifiedSince</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">return</span> axios<span class="token punctuation">.</span><span class="token method function property-access">post</span><span class="token punctuation">(</span>authenticate_url<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string-property property">"client_id"</span><span class="token operator">:</span> client_id<span class="token punctuation">,</span> <span class="token string-property property">"client_secret"</span><span class="token operator">:</span> client_secret <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>

      <span class="token keyword">var</span> auth_token <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token property-access">data</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> graphQLClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GraphQLClient</span><span class="token punctuation">(</span>xray_cloud_graphql_url<span class="token punctuation">,</span> <span class="token punctuation">{</span>
          <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
            <span class="token literal-property property">authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>auth_token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
          <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>

        testexec_ids <span class="token operator">=</span> testExecIssueIds<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">t</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
          <span class="token keyword control-flow">return</span> <span class="token string">'"'</span> <span class="token operator">+</span> t <span class="token operator">+</span> <span class="token string">'"'</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// console.log(testexec_ids);</span>

        <span class="token keyword">const</span> query <span class="token operator">=</span> gql<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token graphql language-graphql"> 
        <span class="token punctuation">{</span>
          <span class="token property-query">getTestRuns</span><span class="token punctuation">(</span><span class="token attr-name">testExecIssueIds</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>testexec_ids<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token attr-name">limit</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>limit<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">,</span> <span class="token attr-name">start</span><span class="token punctuation">:</span> <span class="token class-name"><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>start<span class="token interpolation-punctuation punctuation">}</span></span></span><span class="token punctuation">,</span> <span class="token attr-name">modifiedSince</span><span class="token punctuation">:</span> <span class="token string">"<span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>modifiedSince<span class="token interpolation-punctuation punctuation">}</span></span>"</span>  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token property">total</span>
            <span class="token property">start</span>
        
            <span class="token object">results</span><span class="token punctuation">{</span>
              <span class="token property">id</span>
              <span class="token object">status</span><span class="token punctuation">{</span>
                <span class="token property">name</span>
                <span class="token property">description</span>
              <span class="token punctuation">}</span>
              <span class="token property">comment</span>
              <span class="token object">evidence</span><span class="token punctuation">{</span>
                <span class="token property">filename</span>
                <span class="token property">downloadLink</span>
              <span class="token punctuation">}</span>
              <span class="token property">defects</span>
              <span class="token property">executedById</span>
              <span class="token property">startedOn</span>
              <span class="token property">finishedOn</span>
              <span class="token property">assigneeId</span>
        
              <span class="token object">testType</span><span class="token punctuation">{</span>
                <span class="token property">name</span>
              <span class="token punctuation">}</span>
        
              <span class="token object">steps</span> <span class="token punctuation">{</span>
                  <span class="token property">id</span>
                  <span class="token property">action</span>
                  <span class="token property">data</span>
                  <span class="token property">result</span>
                  <span class="token object">customFields</span> <span class="token punctuation">{</span>
                    <span class="token property">name</span>
                    <span class="token property">value</span>
                  <span class="token punctuation">}</span>
                  <span class="token property">comment</span>
                  <span class="token object">evidence</span><span class="token punctuation">{</span>
                    <span class="token property">filename</span>
                    <span class="token property">downloadLink</span>
                  <span class="token punctuation">}</span>
                  <span class="token object">attachments</span> <span class="token punctuation">{</span>
                      <span class="token property">id</span>
                      <span class="token property">filename</span>
                  <span class="token punctuation">}</span>
                  <span class="token property">defects</span>
                  <span class="token property">actualResult</span>
                  <span class="token object">status</span> <span class="token punctuation">{</span>
                    <span class="token property">name</span>
                  <span class="token punctuation">}</span>
              <span class="token punctuation">}</span>
        
              <span class="token property">scenarioType</span>
              <span class="token property">gherkin</span>
              <span class="token object">examples</span> <span class="token punctuation">{</span>
                  <span class="token property">id</span>
                  <span class="token object">status</span> <span class="token punctuation">{</span>
                      <span class="token property">name</span>
                      <span class="token property">description</span>
                  <span class="token punctuation">}</span>
                  <span class="token property">duration</span>
              <span class="token punctuation">}</span>
        
              <span class="token property">unstructured</span>
              
              <span class="token object">customFields</span> <span class="token punctuation">{</span>
                  <span class="token property">id</span>
                  <span class="token property">name</span>
                  <span class="token property">values</span>
              <span class="token punctuation">}</span>
        
              <span class="token property-query">preconditions</span><span class="token punctuation">(</span><span class="token attr-name">limit</span><span class="token punctuation">:</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token object">results</span><span class="token punctuation">{</span>
                    <span class="token object">preconditionRef</span> <span class="token punctuation">{</span>
                        <span class="token property">issueId</span>
                        <span class="token property-query">jira</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
                    <span class="token punctuation">}</span>
                    <span class="token property">definition</span>
                <span class="token punctuation">}</span>
              <span class="token punctuation">}</span>
              <span class="token object">test</span> <span class="token punctuation">{</span>
                  <span class="token property">issueId</span>
                  <span class="token property-query">jira</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
              <span class="token punctuation">}</span>
              <span class="token object">testExecution</span> <span class="token punctuation">{</span>
                  <span class="token property">issueId</span>
                  <span class="token property-query">jira</span><span class="token punctuation">(</span><span class="token attr-name">fields</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"key"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
              <span class="token punctuation">}</span>      
            <span class="token punctuation">}</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>  
  </span><span class="token template-punctuation string">`</span></span>

      <span class="token keyword control-flow">return</span> graphQLClient<span class="token punctuation">.</span><span class="token method function property-access">request</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">//console.log(JSON.stringify(data['getTestRuns']['results'], undefined, 2))</span>
            <span class="token keyword control-flow">return</span> data<span class="token punctuation">[</span><span class="token string">'getTestRuns'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'results'</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token comment">// testruns.push(data['getTestRuns']['results'])</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error performing query to obtain testruns: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword control-flow">catch</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
      <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token string">'Error on Authentication: '</span> <span class="token operator">+</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token doc-comment comment">/**** main *****/</span>

<span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>

  <span class="token keyword">let</span> configFile <span class="token operator">=</span> <span class="token string">'export_testruns.json'</span>
  <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fs<span class="token punctuation">.</span><span class="token method function property-access">existsSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    fs<span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">,</span> <span class="token string">"{}"</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">let</span> config <span class="token operator">=</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token method function property-access">readFileSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">let</span> modifiedSince <span class="token operator">=</span> config<span class="token punctuation">[</span><span class="token string">'modifiedSince'</span><span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token string">"2021-01-01T00:00:00Z"</span>

  <span class="token comment">// obtain Test Execution issue ids</span>
  <span class="token keyword">let</span> start <span class="token operator">=</span> <span class="token number">0</span>
  <span class="token keyword">let</span> limit <span class="token operator">=</span> <span class="token number">100</span>
  <span class="token keyword">let</span> testexecs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword">let</span> tes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword control-flow">do</span> <span class="token punctuation">{</span>
    tes <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getTestExecutionIds</span><span class="token punctuation">(</span>jql<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit<span class="token punctuation">)</span>
    start <span class="token operator">+=</span> limit
    testexecs<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token spread operator">...</span>tes<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>tes<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>

  <span class="token comment">// obtain the Test Runs for the given Test Execution issue ids, modified since a given data</span>
  <span class="token keyword">let</span> testruns <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  start <span class="token operator">=</span> <span class="token number">0</span>
  <span class="token keyword">let</span> trs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword control-flow">do</span> <span class="token punctuation">{</span>
    trs <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getTestRuns</span><span class="token punctuation">(</span>testexecs<span class="token punctuation">,</span> start<span class="token punctuation">,</span> limit<span class="token punctuation">,</span> modifiedSince<span class="token punctuation">)</span>
    start <span class="token operator">+=</span> limit
    testruns<span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token spread operator">...</span>trs<span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword control-flow">while</span> <span class="token punctuation">(</span>trs<span class="token punctuation">.</span><span class="token property-access">length</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>
  
  <span class="token console class-name">console</span><span class="token punctuation">.</span><span class="token method function property-access">log</span><span class="token punctuation">(</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>testruns<span class="token punctuation">,</span> <span class="token keyword nil">undefined</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  fs<span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span><span class="token string">'testruns.json'</span><span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>testruns<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  config<span class="token punctuation">[</span><span class="token string">'modifiedSince'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">split</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">+</span><span class="token string">"Z"</span>
  fs<span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span>configFile<span class="token punctuation">,</span> <span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre></div>
<p>The previous script will generate a <code>testruns.json</code> file, having an array of JSON objects, each one corresponding to a test run.</p>
<p>Now we need to import it to Splunk. In Splunk we have events; our test runs will be abstract as Splunk events.</p>
<p>In Splunk, we start by creating a new "source type", and name it (e.g., "_xray_graphql"). Go to <code>Settings > Source Types</code> and choose a structured, JSON kind of source. Specify the timestamp fields (e.g., "startedOn", "finishedOn"); these fields should be present on ech test run JSON object.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/source_type.jpg" alt="Create source type in Splunk" loading="lazy"></p>
<p>Then, we define a new data input, by creating a token for the HTTP Event Collector (HEC).
Go to <code>Settings > Data Inputs</code> and select the HTTP Event Collector, and make sure you select the source type created earlier.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/data_input.jpg" alt="Create data input in Splunk" loading="lazy"></p>
<p>We can submit one event or multiple events at once using the HTTP Event Collector instance/token we created. In this case, we'll submit an array with multiple test runs.
I've created a shell script <code>import_testruns_to_splunk.sh</code> to assist me on this.</p>
<div class="remark-highlight"><pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash</span>
 
<span class="token assign-left variable">FILE</span><span class="token operator">=</span><span class="token variable">$1</span>
<span class="token function">curl</span> <span class="token parameter variable">-k</span> https://prd-p-ys24n.splunkcloud.com:8088/services/collector/raw <span class="token parameter variable">-H</span> <span class="token string">"Authorization: Splunk 75db55f8-b89f-4bc5-0000-00000000"</span> <span class="token parameter variable">-X</span> POST <span class="token parameter variable">-H</span> <span class="token string">"Content-Type: application/json"</span> <span class="token parameter variable">-d</span> @<span class="token string">"<span class="token variable">$FILE</span>"</span>
</code></pre></div>
<p>I can then import the test runs as follows.</p>
<div class="remark-highlight"><pre class="language-bash"><code class="language-bash">$ ./import_runs_to_splunk.sh testruns.json
<span class="token punctuation">{</span><span class="token string">"text"</span><span class="token builtin class-name">:</span><span class="token string">"Success"</span>,<span class="token string">"code"</span>:0 <span class="token punctuation">}</span>
</code></pre></div>
<p>These events (i.e., our test runs) will be assigned to the source we identified (e.g., "http2"). We can then use that to filter them later on.</p>
<h2 id="using-splunk-to-analyze-the-test-runs">Using Splunk to analyze the test runs</h2>
<p>Searching for testruns is straightforward.</p>
<p>We can search by events (i.e., our testruns), based on the source... or on the sourcetype... or using both fields; in this case the result wil be the same.</p>
<p><code>source="http2" sourcetype="_xray_graphql"</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_searching.jpg" alt="Searching test runs in Splunk" loading="lazy"></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_date_filtering.jpg" alt="Searching test runs in Splunk filtered by date" loading="lazy"></p>
<h3 id="search-for-successful-ie-passed-test-runs">Search for successful (i.e., "passed") test runs</h3>
<p>We can easily search by the status reported for the test run.</p>
<p><code>source="http2"  sourcetype="_xray_graphql" "status.name"=PASSED</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_search_by_status.jpg" alt="Searching test runs in Splunk by status" loading="lazy"></p>
<h3 id="search-for-test-runs-having-a-certain-comment">Search for test runs having a certain comment</h3>
<p><code>source="http2"  sourcetype="_xray_graphql" comment = "*interesting*"</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_search_by_comment.jpg" alt="Searching test runs in Splunk by comment" loading="lazy"></p>
<h3 id="search-for-test-runs-having-linked-defects">Search for test runs having linked defects</h3>
<p>The following query will obtain the test runs having linked defects, either globally or at step-level.</p>
<p><code>source="http2"  sourcetype="_xray_graphql" ("defects{}"="*" OR "steps{}.defects{}"="*")</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_search_by_defects.jpg" alt="Searching test runs in Splunk by comment" loading="lazy"></p>
<h3 id="search-for-test-runs-for-a-certain-test-issue">Search for test runs for a certain Test issue</h3>
<p><code>source="http2" sourcetype="_xray_graphql" test.jira.key="CALC-100"</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_search_by_test.jpg" alt="Searching test runs in Splunk by Test key" loading="lazy"></p>
<h3 id="chart-top-tests-failing">Chart: Top tests failing</h3>
<p><code>source="http2"  sourcetype="_xray_graphql" status.name="FAILED" | top limit=5 test.jira.key</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_top_tests_failing.jpg" alt="Top tests failing using Splunk " loading="lazy"></p>
<h3 id="chart-top-tests-with-more-runs">Chart: Top tests with more runs</h3>
<p><code>source="http2"  sourcetype="_xray_graphql" | top limit=5 test.jira.key</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_top_tests_by_run_count.jpg" alt="Top tests with more runs using Splunk " loading="lazy"></p>
<h3 id="chart-show-test-results-over-time">Chart: Show test results over time</h3>
<p><code>source="http2"  sourcetype="_xray_graphql" | timechart  count by status.name</code></p>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_results_over_time.jpg" alt="Test results over time using Splunk " loading="lazy"></p>
<h3 id="pivot-tables-show-test-runs-count-for-tests-grouped-by-status">Pivot tables: show test runs count for Tests, grouped by status</h3>
<p><img src="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/splunk_pivot.jpg" alt="Pivot tables using Splunk " loading="lazy"></p>
<h2 id="key-findings">Key findings</h2>
<p>These are a sum of my findings for this brief exercise:</p>
<ul>
<li>we can send an event or several events in bulk by HTTP to Splunk (we can also upload the test runs as a JSON file)</li>
<li>in Splunk test runs become easily searchable</li>
<li>in Splunk different types of charts can be done based on fields of the test runs</li>
<li>Splunk supports pivot tables based on filed on the test runs</li>
<li>it's possible to use drill-down on the charts and tables</li>
</ul>
<h2 id="final-considerations">Final considerations</h2>
<p>This was a very interesting exercise. First, it showed how easy it could be to export information from this test management tool to another solution where we can analyze data in further detail, and eventually correlate it with something else.</p>
<p>Splunk provides powerfull capabilities for searching, analyzing, and visualizing data. Events can be anything we want; in this case, we used events as an abstraction for our test runs.</p>
<p>Additional work needs to be performed to have better control over the data that is imported and how it is represented in Splunk.</p>
<h2 id="useful-references">Useful references</h2>
<ul>
<li><a href="https://docs.getxray.app/display/XRAYCLOUD/GraphQL+API">Xray cloud GraphQL API</a></li>
<li><a href="https://www.splunk.com/en_us/products/splunk-cloud-platform.html">Splunk Cloud Platform</a></li>
<li><a href="https://docs.splunk.com/Documentation/Splunk/7.3.0/Data/UsetheHTTPEventCollector">Splunk's HTTP Event Collector</a></li>
</ul>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/exporting-testruns-to-splunk/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Contributing to an open-source testing project: Playwright]]></title>
            <link>https://www.sergiofreire.com/post/playwright-open-source-contrib</link>
            <guid>https://www.sergiofreire.com/post/playwright-open-source-contrib</guid>
            <pubDate>Mon, 18 Jul 2022 14:00:00 GMT</pubDate>
            <description><![CDATA[<p>I'm very excited to have sucessfuly contributed to <a href="https://playwright.dev">Playwright</a>, a cross-platform, cross-language, open-source end-to-end testing framework backed by Microsoft and a large community. From here onwards, you can embed additional information (e.g., screenshots, notes, custom metadata) on the tests so that it can be shown later on in Jira, for example.</p>
<p>This is mostly a story about learning. In this article I'll go over the purpose of my contribution, how you can take advantage of it with concrete Playwright examples, and then I'll share what I learned throughout this process.</p>
<h2 id="purpose">Purpose</h2>
<p>As team members (developers, testers) writing test automation scripts to verify expected behaviour in our web sites/apps, we implement a set of actions and one or more assertions. Many times this is done at the UI layer.
After running our tests (Selenium/Webdriver, Cypress, or Playwright based) we get a report that we can use to analyze the test results, either by looking at an HTML kind of report or by uploading it to a central place/tool where we can analyze it in more detail, for example to track historical results.</p>
<p>But the value provided by our test scripts is not just limited to the pass/fail result and the duration of the test. We need to be able to understand what happened, so we can diagnose the failure and get to the actual root-cause.
Sometimes we may need to embed additional information, either as text or to a specific variable Screenshots are also quite useful, especially upon failures.</p>
<p>Initally this effort was mainly targeted at enabling this information for <a href="https://getxray.app">Xray</a>, a well-known test management tool. But this need goes beyond this specific tool, and many users out there can also benefit from it if we (I and the community) could make it broad enough.</p>
<h2 id="embedding-additional-information-in-your-tests">Embedding additional information in your tests</h2>
<p>In Playwright, you can implement a test using <code>test()</code> as shown in the following example for validating the successful login flow in a certain website.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">LoginPage</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"../models/Login.js"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

test<span class="token punctuation">.</span><span class="token method function property-access">beforeEach</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> page <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">goto</span><span class="token punctuation">(</span><span class="token string">'https://robotwebdemo.herokuapp.com'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>


test<span class="token punctuation">.</span><span class="token method function property-access">describe</span><span class="token punctuation">(</span><span class="token string">"Login validations"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>

    <span class="token function">test</span><span class="token punctuation">(</span><span class="token string">'Login with valid credentials'</span><span class="token punctuation">,</span> <span class="token keyword">async</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> page <span class="token punctuation">}</span><span class="token punctuation">,</span> testInfo</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> loginPage <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LoginPage</span><span class="token punctuation">(</span>page<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">navigate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">login</span><span class="token punctuation">(</span><span class="token string">"demo"</span><span class="token punctuation">,</span> <span class="token string">"mode"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">const</span> name <span class="token operator">=</span> <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">getInnerText</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">expect</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toBe</span><span class="token punctuation">(</span><span class="token string">'Login succeeded. Now you can logout.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token punctuation">}</span>
</code></pre></div>
<p>But how can we provide additional information that can be embed on the generated report (e.g., JUnit XML), so that other tools can take advantage of?</p>
<p>Some time ago, I've helped to define an <a href="https://docs.getxray.app/display/XRAYCLOUD/Taking+advantage+of+JUnit+XML+reports#TakingadvantageofJUnitXMLreports-XrayextendedJUnitformatLinkingTestswithRequirements">evolved JUnit XML format</a> that could provide the means to embed additional information at testcase level. JUnit XML doesn't have a very strict format but the fact that. I've quickly shared this format through the JUnit team and no big blockers appeared. Of course that only tools that can understand this evolved format, can then take advantage of it. Other tools should simply ignore them, except for one or another that have strict schema validation (whatever that schema may be).</p>
<p>This evolved JUnit XML format is now supported by Xray, TestRail (in-progress), and possibly other tools.</p>
<p>Then the question becomes: <em>How can we make our custom metadata into the JUnit XML report?</em></p>
<p>Well, that requires testing frameworks to have a way of specifying this and somehow associate it to the current test.</p>
<p>That is precisely the scope of the contribution I've made for Playwright Test, the test runner provided by the Playwright team.</p>
<h3 id="configuration">Configuration</h3>
<p>To take advantage of the new capabilities which allow us to embed additional information at test level, first we need to configure Playwright to use the JUnit reporter; we then also need to configure a few additional settings. More info <a href="https://playwright.dev/docs/test-reporters#junit-reporter">here</a>.</p>
<p>In the Playwright configuration file (e.g., <code>playwright.config.js</code>), we would have something such this code snippet.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token comment">// JUnit reporter config for Xray</span>
<span class="token keyword">const</span> xrayOptions <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token comment">// Whether to add &#x3C;properties> with all annotations; default is false</span>
  <span class="token literal-property property">embedAnnotationsAsProperties</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>

  <span class="token comment">// By default, annotation is reported as &#x3C;property name='' value=''>.</span>
  <span class="token comment">// These annotations are reported as &#x3C;property name=''>value&#x3C;/property>.</span>
  <span class="token literal-property property">textContentAnnotations</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'test_description'</span><span class="token punctuation">,</span> <span class="token string">'testrun_comment'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>

  <span class="token comment">// This will create a "testrun_evidence" property that contains all attachments. Each attachment is added as an inner &#x3C;item> element.</span>
  <span class="token comment">// Disables [[ATTACHMENT|path]] in the &#x3C;system-out>.</span>
  <span class="token literal-property property">embedAttachmentsAsProperty</span><span class="token operator">:</span> <span class="token string">'testrun_evidence'</span><span class="token punctuation">,</span>

  <span class="token comment">// Where to put the report.</span>
  <span class="token literal-property property">outputFile</span><span class="token operator">:</span> <span class="token string">'./xray-report.xml'</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">reporter</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span><span class="token string">'junit'</span><span class="token punctuation">,</span> xrayOptions<span class="token punctuation">]</span> <span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span><span class="token property-access">exports</span> <span class="token operator">=</span> config<span class="token punctuation">;</span>

</code></pre></div>
<p>After we've configured Playwright, we can focus on writting the Playwright test scripts.</p>
<p>To add custom metadata, we take advantage of Playwright annotations. We can access the <code>TestInfo</code> object for that, either by using <code>test.info()</code> or by specifying it as a parameter <code>test('',  async ({}, testInfo) => {});</code>.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token function">test</span><span class="token punctuation">(</span><span class="token string">'example1'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> page <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
  test<span class="token punctuation">.</span><span class="token method function property-access">info</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">annotations</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'issue'</span><span class="token punctuation">,</span> <span class="token literal-property property">description</span><span class="token operator">:</span> <span class="token string">'XPTO-1'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">test</span><span class="token punctuation">(</span><span class="token string">'example1'</span><span class="token punctuation">,</span>  <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> page <span class="token punctuation">}</span><span class="token punctuation">,</span> testInfo</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
  testInfo<span class="token punctuation">.</span><span class="token property-access">annotations</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'issue'</span><span class="token punctuation">,</span> <span class="token literal-property property">description</span><span class="token operator">:</span> <span class="token string">'XPTO-1'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// ...</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre></div>
<p>In this case, assuming we have Xray Test Management, it understands at least these annotations:</p>
<ul>
<li><strong>test_key</strong>: map this test code to an existing Test issue in Xray having the specified issue key;</li>
<li><strong>test_id</strong>: map this test code to an existing Test issue in Xray having the specified issue id; not that much used though;</li>
<li><strong>test_summary</strong>: the summary of the Test issue that will be autoprovisioned in Xray;</li>
<li><strong>test_description</strong>: the description of the Test issue that will be autoprovisioned in Xray;</li>
<li><strong>tags</strong>: list of one or more tags that will be added as labels on the corresponding Test issue in Xray, delimited by comma;</li>
<li><strong>testrun_comment</strong>: text comment to add to the test run as associated with the Test issue in Xray;</li>
<li><strong>requirements</strong>: issue keys of one or several stories/requirements in Xray, delimited by comma.</li>
</ul>
<p>The <code>test_description</code> annotation as can be a lengthy text, will be encoded slightly different on the JUnit XML report; that's precisely the reason we have identified it as text on the <code>textContentAnnotations</code> setting in our config file shown above.</p>
<p>As we also aim to embed files (e.g., screenshots, other relevant evidence) and associate them to our test result, we need to identify the annotation we'll use for this using the <code>embedAttachmentsAsProperty</code> setting; in our case, we'll use the <code>testrun_evidence</code>.</p>
<h3 id="link-the-test-to-a-story-or-a-requirement-in-jira">Link the test to a story or a requirement in Jira</h3>
<p>In this case, we're covering an existing story with this test, right from the test code.
For that we add an annotation <code>requirements</code> having the value of the issue key of the item (story, requirement, or similar) in Jira; usually a test is linked to just one story/requirement.
This information will be added as a <code>&#x3C;property></code> on the underlying <code>&#x3C;testcase></code> element of the JUnit XML reporter produced by the built-in junit reporter, which then tools can take advantage of.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token function">test</span><span class="token punctuation">(</span><span class="token string">'Login with valid credentials'</span><span class="token punctuation">,</span> <span class="token keyword">async</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> page <span class="token punctuation">}</span><span class="token punctuation">,</span> testInfo</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
        testInfo<span class="token punctuation">.</span><span class="token property-access">annotations</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'requirements'</span><span class="token punctuation">,</span> <span class="token literal-property property">description</span><span class="token operator">:</span> <span class="token string">'CALC-5'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">const</span> loginPage <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LoginPage</span><span class="token punctuation">(</span>page<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">navigate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">login</span><span class="token punctuation">(</span><span class="token string">"demo"</span><span class="token punctuation">,</span> <span class="token string">"mode"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">const</span> name <span class="token operator">=</span> <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">getInnerText</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">expect</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toBe</span><span class="token punctuation">(</span><span class="token string">'Login succeeded. Now you can logout.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div>
<p>If, for example, you're using <a href="https://www.getxray.app">Xray</a> for test management in Jira, then you whenever you upload the JUnit XML report (e.g., by REST API, Jenkins plugin, or other CI/CD tool), Xray will process it and will make the linkage/coverage between the corresponding Test and the identified story/requirement.</p>
<p>Here's an example of how it would look like in your Jira instance.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/playwright-open-source-contrib/test_covering_story.jpg" alt="Screenshot of Jira with Xray showing the test linked to an existing story" loading="lazy"></p>
<h3 id="attach-screenshots-andor-other-files">Attach screenshots and/or other files</h3>
<p>In this case, we want to add some screenshot or even some other evidence as files associated with a given test result.</p>
<p>There are multiple ways to achieve it; internally they all work the same way as they'll be used to fill out an <code>attachments</code> array on the related <code>TestInfo</code> object.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token function">test</span><span class="token punctuation">(</span><span class="token string">'Login with valid credentials'</span><span class="token punctuation">,</span> <span class="token keyword">async</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> page <span class="token punctuation">}</span><span class="token punctuation">,</span> testInfo</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> loginPage <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LoginPage</span><span class="token punctuation">(</span>page<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">navigate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">login</span><span class="token punctuation">(</span><span class="token string">"demo"</span><span class="token punctuation">,</span> <span class="token string">"mode"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// Capture a screenshot to a local file and attach it</span>
        <span class="token keyword">const</span> path <span class="token operator">=</span> testInfo<span class="token punctuation">.</span><span class="token method function property-access">outputPath</span><span class="token punctuation">(</span><span class="token string">'screenshot.png'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">screenshot</span><span class="token punctuation">(</span><span class="token punctuation">{</span> path <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        testInfo<span class="token punctuation">.</span><span class="token property-access">attachments</span><span class="token punctuation">.</span><span class="token method function property-access">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'screenshot.png'</span><span class="token punctuation">,</span> path<span class="token punctuation">,</span> <span class="token literal-property property">contentType</span><span class="token operator">:</span> <span class="token string">'image/png'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// attach a text file; in this case we're writing the file first to the FS</span>
        <span class="token keyword">const</span> file <span class="token operator">=</span> testInfo<span class="token punctuation">.</span><span class="token method function property-access">outputPath</span><span class="token punctuation">(</span><span class="token string">'evidence1.txt'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">writeFileSync</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> <span class="token string">'hello'</span><span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword control-flow">await</span> testInfo<span class="token punctuation">.</span><span class="token method function property-access">attach</span><span class="token punctuation">(</span><span class="token string">'evidence1.txt'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> file<span class="token punctuation">,</span> <span class="token literal-property property">contentType</span><span class="token operator">:</span> <span class="token string">'text/plain'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// attach a text content as file, without ever creating it</span>
        <span class="token keyword control-flow">await</span> testInfo<span class="token punctuation">.</span><span class="token method function property-access">attach</span><span class="token punctuation">(</span><span class="token string">'evidence2.txt'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token maybe-class-name">Buffer</span><span class="token punctuation">.</span><span class="token keyword module">from</span><span class="token punctuation">(</span><span class="token string">'world'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">contentType</span><span class="token operator">:</span> <span class="token string">'text/plain'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">const</span> name <span class="token operator">=</span> <span class="token keyword control-flow">await</span> loginPage<span class="token punctuation">.</span><span class="token method function property-access">getInnerText</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">expect</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toBe</span><span class="token punctuation">(</span><span class="token string">'Login succeeded. Now you can logout.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div>
<p>Here's an example of how it would look like in Jira, in case you have Xray and have uploaded the JUnit XML report.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/playwright-open-source-contrib/testrun_with_evidence.jpg" alt="Screenshot of Jira with Xray showing the attached fikes on the test run" loading="lazy"></p>
<p><img src="https://www.sergiofreire.com/assets/blog/playwright-open-source-contrib/testrun_screenshot.jpg" alt="Screenshot of Jira with Xray showing the attached screenshot on the test run" loading="lazy"></p>
<p>If we're focused on screenshots, Playwright can be configured to take screenshots on every test, or even better: if a test fails.</p>
<p>For the latter, we would adjust our Playwright config file as follows and everything will "automagically" work, without having to change the existing test code base.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token spread operator">...</span>
<span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">reporter</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">[</span><span class="token string">'junit'</span><span class="token punctuation">,</span> xrayOptions<span class="token punctuation">]</span> <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token literal-property property">use</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">screenshot</span><span class="token operator">:</span> <span class="token string">'only-on-failure'</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span><span class="token property-access">exports</span> <span class="token operator">=</span> config<span class="token punctuation">;</span>
</code></pre></div>
<h2 id="learnings">Learnings</h2>
<p>After this process, I do have some learnings to share. Some of them are not new but I think it's worth mentioning them and reflecting a bit about them.</p>
<h3 id="embrace-the-challenge">Embrace the challenge</h3>
<p>Contributing to a major and well-known project, with a large code base, using a language that you don't know (i.e., TypeScript), and a set of practictes you aren't fully aware of, can be daunting. Besides, you see yourself trying to contribute to a project having people with much more expertise than you.
Well, does this make it impossible? Not really.
I told to myself, lets embrace the challenge and use it to learn more about Playwright, TypeScript, and also about interacting/contributing to a well-established project.
It all started by an initial ticket/issue raised on GH, to get discussion started.</p>
<h3 id="from-a-working-prototype-to-prod-is-a-long-road">From a working prototype to prod is a long road</h3>
<p>Making a working prototype is sometimes quite easy; you can make a hack to the existing code that makes what you need. But making something that can get to production and be used successfuly by others requires some additional work.</p>
<p>We need to:</p>
<ul>
<li>make your contribution not so specific</li>
<li>ensure to provide some automated tests along with your code changes</li>
<li>ensure (good) documentation</li>
<li>be open for feedback; be kind but be able to mention the points that matter</li>
</ul>
<h3 id="working-in-a-totally-remote-environment-without-calls-is-possible">Working in a totally remote environment, without calls, is possible</h3>
<p>Working in a fully remote environment, is natural for most open-source projects. Playwright is a projected backed by Microsoft, so there is a team behind it. Besides, there is also the broad Playwright team made by all of its contributors.</p>
<p>During the discovery phase/initial <a href="https://github.com/microsoft/playwright/issues/9348">conversations</a>, or even during code review, having feedback on a reasonable time frame is key for success.</p>
<p>I was surprised by how helpful the team was, not just because I received feedback on time but also because they helped me throughout the process. I was a first time commiter on the project and I was also writing code in TypeScript for the first time... and it all went fine!</p>
<h3 id="what-made-the-contribution-easier">What made the contribution easier</h3>
<p>Besides the help from the Playwright community/maintainers, the project itself is set up in a way that eases contribuitions. First, the contribution process is described which removes some initial friction.
Second, the build process makes linting on the code and on the documentation; besides, a full battery of around 8.000 (!) automated test scripts ensures a good enough coverage. There are additional checks (e.g., for dependencies) that provide one additional layer of confidence.</p>
<h3 id="async-can-be-challenging">Async can be challenging</h3>
<p>Sometimes you just depend on yourself; actually, most times we probably depend on others.
Collaboration is something positive but we can have some delays that hardens this process.</p>
<p>During this process, the Playwright team was pretty fast giving feedback; it's not real-time but it was quite good.</p>
<p>However, sometimes we have other priorities going on, some process/legal flows we have to follow, etc. And all of this can delay the whole contribution. Delays can cause, besides a certain frustration, also additional lack of efficency due to context switches.</p>
<h3 id="you-start-with-a-problem-or-a-goal-and-you-end-up-finding-another-ones">You start with a problem or a goal and you end up finding another ones</h3>
<p>Sometimes you start by fixing some bug that you are well aware of, or implementing the feature that you imagined (e.g., such as the one I highlighted here). But the process is never a straight road where you go blind towards your destiny. During your trip you'll notice other things (perhaps by serendipity - "lucky findings"), and that's great because it means that you're not just coding/fixing something; you're learning through the way! I've found a minor encoding problem in character data (i.e. "CDATA") in the JUnit XML report, which we discussed, and I've fixed. Meanwhile, I found another issue that I've yet to report. All this is normal, because we as humans and smart people learn... and as we learn we seek to improve.</p>
<h2 id="in-sum">In sum</h2>
<p>Contributing to a major project can be overwhelming, especially whenever we have limited context about it, its tecnologies, or even the team.
But we can always learn; as testers, or as people with testing skills in their heart and soul, we're already used to it.</p>
<p>In the end, this was both a learning exercise and also somethign that hopefuly will be used by many of you out there.</p>
<p>It feels great to use open-source software; it feels greater whenever we can contribute to it!</p>
<p>Feel free to reach out in case you have further suggestions :)</p>
<p>Note: in case you want to see a full tutorial showcasing Playwright integrated with Jira and Xray, please have a look at <a href="https://docs.getxray.app/display/XRAYCLOUD/Testing+web+applications+using+Playwright">this tutorial</a>.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/playwright-open-source-contrib/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Automation is not the goal]]></title>
            <link>https://www.sergiofreire.com/post/automation-is-not-the-goal</link>
            <guid>https://www.sergiofreire.com/post/automation-is-not-the-goal</guid>
            <pubDate>Mon, 24 Jan 2022 11:50:00 GMT</pubDate>
            <description><![CDATA[<p>Some teams and some people think that automation is the goal. But is it?
Let's find out.</p>
<h2 id="why-do-we-automate">Why do we automate?</h2>
<p>I've seen many teams come up with test automation due to these reasons:</p>
<ul>
<li>someone told us to</li>
<li>we've seen other doing it</li>
<li>we have the feeling that it will help us</li>
</ul>
<p>Interestingly enough, all of these are not based on real needs identified by the team that serve a clear purpose.</p>
<h2 id="why-should-we-automate">Why should we automate?</h2>
<p>Automation comes as a consequence of a broader strategy. The same applies to test automation, a smaller set of automation (I would recommend doing some search on test automation strategy).
These are some reasons why, IME, we should automate, in terms of test automation:</p>
<ul>
<li>Have fast feedback loops: know ASAP if a change/fix we're adding into the product, is breaking some existing functionality or if is impacting on some relevant KPIs, such as performance;</li>
<li>Have time to explore: even though I came to the conclusion that test automation and exploratory testing are not exclusive, if we have a bunch of automated checks and automated KPIs in place, then we can have some "free time" to explore the system freely and uncover things we didn't expect.</li>
</ul>
<p>These are, IMO, the two main reasons. But there are more:</p>
<ul>
<li>Assist on finding the root cause of problems, if the tests are focused enough and have ways to get the context;</li>
<li>Augment testing with tooling, by providing us insights that otherwise would be hard or impossible to get;
performance/load testing are a good example of that.</li>
</ul>
<p>If you're thinking about costs, or if you have been asked about, of course there are costs related to implementing proper automation. But there are even greater costs whenever you neglect it. Long story short: these are good costs for sure, as they will enable teams to become more aware and more agile with time.</p>
<h2 id="the-goal-is-something-else">The goal is something else</h2>
<p>In my experience, automation is never the goal. Writing lines of code is never the goal.</p>
<p>What concerns test automation, we have to understand the needs that we aim to overcome.
There are different types of testing, at different levels and layers, that we can implement.
Test automation scripts are not the responsibility of a single person, unless you're the only one in that project.
Test automation is code and thus should be close to the heart of developers; it also follows the same rules. Developers need to implement some checks/validations and track some quality metrics from the start, including performance, security and accessibility.
If we have people dedicated just to test automation, it would be great if they can complement the already existing testing that is in place, by leveraging more complex or risky testing scenarios/environments for example.</p>
<p>Whenever we think about test automation, and if all that comes to "We have to implement some selenium tests to check is "everything" is ok" or "Automated tests? That's to the test automation team!", then it's a sign we have to rethink it all.</p>
<p>In terms of the why, a conclusion is never the why, I think. The team needs to discuss where they stand, what are their current blockers and where they want to go. How can we make the team be more efficient? How can we depict a problem as fast as we can? Can we have additional sources of information that can assist us? In my perspective, these are some examples of the questions we should ask ourselves. Then, of course, we can come to the "hows".
In terms of the "who", testing and quality are responsibility of everyone, where some can contribute in different depth and with different perspectives.
Testers can always contribute with their expertise on exposing unknowns and risks that may matter; that's one of their main values. It's not about the test automation scripts.</p>
<p>The goal is to make the team more aware about how their current system/product behaves and how changes we're crafting impact those, so the team can act quickly in case something is getting broken or something is getting negatively impacted (e.g., performance, code quality).</p>
<p><strong>The goal is, and probably most of all, for the team to be more confident.</strong></p>
<p>Only confident teams take risks and try out new things and learn from that, ultimately improving the product.
What about your team? Is test automation a goal? Or a consequence of something broader, that involves the whole team and making that team more confident and more agile?</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/automation-not-the-goal/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Can we postpone quality?]]></title>
            <link>https://www.sergiofreire.com/post/quality-and-time</link>
            <guid>https://www.sergiofreire.com/post/quality-and-time</guid>
            <pubDate>Mon, 10 Jan 2022 11:30:00 GMT</pubDate>
            <description><![CDATA[<p>Can we postpone quality?</p>
<p>The speed of change is increasing, the demand for change is also increasing.
Knowing that quality is multidimensional, and that time is also part of those dimensions, we need to handle quality matters wisely, by being prepared as much as we can.
The decisions we take today, or that we don't take, will affect quality.
Teams continuously face challenges; some may be more aware of this than others.</p>
<h2 id="the-performance-challenge">The performance challenge</h2>
<p>Let's look at a real life example, based on feedback collected from some teams I've worked with.
As mentioned in a <a href="/post/some-perspectives-on-quality">previous article</a>, some time ago, we did an assessment with several teams. One of our goals was to understand how these teams were approaching testing.</p>
<p>From all the conversations we had, we could realize several things (I'll just enumerate a few):</p>
<ul>
<li>having a product "without bugs" was a concern</li>
<li>performance was also a concern, with different levels of importance, in some teams</li>
</ul>
<p>Let's focus on the performance concern as I think it can be used as a reference.</p>
<p>Depending on the nature of the product, how it is deployed and maintained, how it used, and how often, performance as a quality criterium can become more or less relevant.</p>
<h3 id="the-we-have-already-dealt-with-it-team">The "we have already dealt with it" team</h3>
<p>One of the teams had struglled with performance many times in the past. Once in a while, a performance issue would popup.
There were several reasons for it (internal and external), and some of them were actually kinda of good ones - usage in more high-demanding scenarios, with thousands of concurrent users and with unthought usage scenarios.
I remember the team addressed each performance issue, one by one like in a firefighting mode. Performance wasn't being tracked consistently; there were some ad hoc prrocess to measure some aspects of performance, but a dedicated performance environment, where performance could be tracked continuously only came later on.</p>
<p>Whenever the team decided to tackle performance, not just by reworking some core aspects of the product but also by implementing a whole performance approach with a dedicated environment, gains started to arise and performance issues became very rare.
Confidence increased a lot but we know that pursuing quality is a neverending story.</p>
<h3 id="the-data-team">The "data" team</h3>
<p>This very small team focused on processing data was currently in firefighting mode.
A dedicated performance environment is missing and so are continuous measurements on performance.
The team is overloaded trying to address performance issues as these keep coming, as the product is being used in numerous and uncontrolled ways.
This in fact needs some thoughts: of course we need to measure performance upon changes in the product but we also need to have come clarifications about product usage. A clear commitment (spec if you want), about the minimals the product shall handle was missing. And more than that: there were no ways of controlling/limiting the source data which could ultimately affect that specific product usage but also the overall product related infrastructure.</p>
<h3 id="not-worried-at-all-kind-of-teams">"Not worried at all" kind of teams</h3>
<p>Several teams were in this category. They hadn't faced any significant performance challenges so far, therefore they were not worried; performance was not a concern on their daily activities.</p>
<h2 id="meet-the-quality-mountains-model">Meet the "Quality Mountains" model</h2>
<p>Even though what contributes to quality (i.e. quality criteria) changes with time, teams will face some common challenges.
Sometimes it may be performance, other times it's security, other times it's something else.
We need to be prepared to handle those challenges by not just shifting left, but also by embedding that as a concern in our day to day activities.</p>
<p>Performance, for example, is an issue that sooner or later will come to almost every single team. The later it comes, the harder will be eventually to address it as it may require rebuilding the architecture of the application, for example. Don't expect easy fixes, like changing some flags in the compiler or in the JVM; those are possible but most times they are just workarounds, which don't solve performance problems that are due to the way the solution was built.</p>
<p>I realized that similar challenges will come to teams at different times, with different intensity and different relevance.
Not all teams will have the same kind of challenges but we can see similar challenge patterns across teams, such as performance, security, accessibility, among other.</p>
<p>Therefore, I came with the "Quality Mountains" model, which I tried to sketch in a very rough way :-)</p>
<p><img src="https://www.sergiofreire.com/assets/blog/quality-and-time/quality_mountains.jpg" alt="Quality Mountains model" loading="lazy"></p>
<p>Each mountain represents a quality challenge. There are mountains of different types (here annotated with distinct letters), representing different quality aspects you have to address. No mountain is the same, even whenever they are of the same type.
Mountains exist ahead of us, even if we don't see them coming. Sometimes we may already be climbing one these mountains and drowned dealing with all the technical and non-technical aspects we have to overcome.</p>
<p>The sooner we prepare to climb them, the smooth our trip ahead of us will be. Sometimes we may use some telescope, or send some probes, to see what's coming ahead (up to a point).</p>
<p>Our overall software development &#x26; maintenance trip is not flat; flatiness is an illusion.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>For some teams, a given challenge, such as performance, may be more or less relevant in the context of their product and its usage scenarios. That's fine. However, we need to realize that we're in a road, at full speed ahead and challenges will come, no matter what.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/quality-and-time/quality_cannot_be_postponed.jpg" alt="Quality cannot be postponed" loading="lazy"></p>
<p>So, we cannot postpone handling quality because quality challenges, no matter if we're talking about performance, security, or other, they will come at fast speed. Are we prepared to handle them somehow? Can we do something about it? Can we start perhaps embedding performance/security/and other quality related practices to diminish risk, its probability and its impact?</p>
<p>What about taking some time to reflect within the team about those risks we're ignoring or not properly handling right now? Then define some countermeasures, such as deepen knowledge in certain areas, so our (individual and team) skills are improved and we can tackle the problem with thoughful actions.</p>
<p>Remember that challenges ("quality mountains") will come, even if you don't see them ahead.
Are you and your team already preparing for them?</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/quality-and-time/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[ATOMYQ: Another Testing Oriented Model w/ Yummy Quality]]></title>
            <link>https://www.sergiofreire.com/post/atomyq-another-testing-oriented-mother-with-yummy-quality</link>
            <guid>https://www.sergiofreire.com/post/atomyq-another-testing-oriented-mother-with-yummy-quality</guid>
            <pubDate>Tue, 21 Dec 2021 17:00:00 GMT</pubDate>
            <description><![CDATA[<p>The third edition of OmniTestingConf happened last month. This edition was themed around the Native Quality Management idea, and several speakers brought their insights about ways of improving quality together and having it as ongoing concern of all team members.</p>
<p>During my talk, I covered the richness of "quality", all the possible meanings we can have related to it. But also, what "value" means and where does it come from.</p>
<p>I came up with a new model: <strong>ATOMYQ: Another Test Oriented Model with Yummy Quality</strong> :-)</p>
<p>But why do we need another model? And what does it mean?
Well, first models are ways to abstract some part of reality; they are ways for us to explain ideas and start conversations. They don't need to be accurate but they are nevertheless quite useful, because they can highlight concepts and how they relate to one another.</p>
<h2 id="meet-atomyq">Meet ATOMYQ</h2>
<p>ATOMYQ is a model that centers testing around 3 main players: Product, Team and (each) Person.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/atomyq-another-testing-oriented-model-with-yummy-quality/cover.jpg" alt="atomyq model diagram" loading="lazy"></p>
<h3 id="team">Team</h3>
<p>All starts with the team.
The team as a whole has a set of diverse <strong>skills that require learning and practice</strong>. Skills are used to craft our product, in all its dimensions, and all its assets.
Unfortunately for us there are constraints. Constrains that will influence our skills, their intensity, their extensity, their reach.
Testing also makes use of skills, many skills in fact (technical and "soft" skills). All of those are engineering skills.
Testing is also applied into our other skills whenever we exercise them, i.e., we are only excellent coders if we care performance while coding algorithms, for example.
All of this is the reason why <strong>team skills</strong> are so important, and why we should care about improving and fostering a knowledge culture. Investing in the team, is investing on the product and its value in broad sense.</p>
<p>Testing uses different sources of information to uncover bugs and opportunities in our product. Well, let me clarify the previous sentence.
First, we use data... and some of that data is actually valuable information; a written specification is just a source of data, not a scientific, closed fact.
Second, testing doesn't exist in the void... or alone. Testing is performed by humans that can make use of skills, tools, automation and all possible means to assist them.</p>
<p>Ideally, testing should be performed by the team, in everything that the team does or doesn't. But testing, as a brain driven activity, ultimately runs inside each person. Each one of us will have different insights, different backgrounds that are essential to explore our product and its idealization.</p>
<h3 id="product">Product</h3>
<p>Many times we think we pack value into the binares we build or the code we write. Well, <strong>tt's just not about the code we build...</strong> it's everything related to product, including documentation, all enabling kind of materials, and more.</p>
<p>What we build, how we build and what we decide not to build are the foundations of product attributes.
Our product as a whole has a bunch of <strong>emergent properties</strong> and attributes, some of them are called quality attributes in the sense that they contribute somehow to a given view of quality.</p>
<p>Nowadays I see the release frequency, i.e. how frequently we release updates on our product, also as an attribute of the product. The quality adjective that can perhaps frame it is: <strong>freshness</strong>.</p>
<p>On the ATOMYQ diagram we can depict bugs and opportunies as living in the product; I struggle to position them to be honest, but that will require some additional thoughts.</p>
<h3 id="person">Person</h3>
<p>Each person, as a unique individual, weights differently product attributes to come up with <strong>value</strong>.</p>
<p>This not only changes with whoever is involved but also depends on the context, and on the time.
Example: if a person was robbed/scammed up recently, maybe security now is one of the attributes that matter most for that person, at least for the next couple of days/weeks!</p>
<p>Quality also informs testing, and thus influences it, as we'll pursue testing focused around the quality attributes that matter most.</p>
<h2 id="other-things-worth-mentioning">Other things worth mentioning</h2>
<p>Our product assets, which are diverse, come from the team as a whole, and not from a single individual. It's the skills within the team that shape our product. Testing is mostly done by the team members but not only; other people, including our users, also test our products.</p>
<p>Testing is not used just to find bugs; I like to think on testing from a positive perspective and think more about opportunities for increasing the value of our products.</p>
<p>As a team sport, testing also shapes how we think and thus improves our skills because as we become aware of potential problems, it changes what we do and how we craft our product assets in the future.
So yes, if we think on testing as something happening at team level, connected to the team skills, then yes, testing improves quality on mid/long term. This may be controversial, I understand, but it's my experience has someone that worked having different roles within product development teams, or whenever working with some external ones.</p>
<p>If we think testing as something done by a given person having the "tester" role, then we can see more testing as a bug reporting kinda of activity. Finding bugs is indeed important but we need to move from there and actually improve quality. If testers become more and more quality coaches, they start influencing the team and we'll see improvement on the skills within the team, ultimately leading to the overall improvement of quality. Still testing itself is crucial, but as a whole team concern. Well, that's how I see it right now.</p>
<h2 id="in-sum">In sum</h2>
<p>ATOMYQ presents the interactions about different players in organizations that are helping others achieve some goals, through the value provided by their products.</p>
<p>Quality is multidimensional, time dependent and view dependent. We don't pack quality. We work with quality in mind, so what we do, shaped by the skills that we have and that the team has, will lead to better product assets.</p>
<p>The goal is working torwards increased team confidence, aligned with better team understanding of what value means and what blocks us from delivering/caring for/improving it.</p>
<p>Testing for sure help us as it is at the center of ATOMYQ, close to opportunities and bugs we cannot ignore. However, testing is not atomic. Instead, it's something we do, more or less explicitly, continuosly because ultimately we use it to improve our knowledge and the art and engineering that is building products, that people use, love, and even pay for.</p>
<p>Skills are essential; testing itself is a broad skill that can help us not only become more informed but also look at things differently, wisely, with value and risk in mind. Looking not just about where we stand, but where we want to go and what we can do to get us there.</p>
<p>Happy holidays/merry christmas, with quality time. And I leave "quality time" definition to yourself, as I'm sure that noone better than you will know the best answer that fits your context :)</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/atomyq-another-testing-oriented-model-with-yummy-quality/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[EuroSTAR 2021: Working with teams to improve quality]]></title>
            <link>https://www.sergiofreire.com/post/eurostar-2021-working-with-teams-to-improve-quality</link>
            <guid>https://www.sergiofreire.com/post/eurostar-2021-working-with-teams-to-improve-quality</guid>
            <pubDate>Fri, 01 Oct 2021 18:00:00 GMT</pubDate>
            <description><![CDATA[<p>EuroSTAR 2021 is over and there is so much to reflect after it. Back in March, I helped out and I even described the <a href="/post/eurostar-testing-conference-review-process">process that I followed</a>.
I participated on EuroSTAR with a <a href="https://conference.eurostarsoftwaretesting.com/event/2021/working-with-development-teams-to-improve-quality/">talk around the "engage"</a> theme. What better than talk about engaging in our own teams in order to improve quality from multiple perspectives?</p>
<p>I started with this question: "Improving quality is a doomed project... or is there a way out?".</p>
<p>During the talk I enumerated some real exercises tthat we've done to overcome this challenge and that you can perhaps try out in your team.
In this article I won't go into the details about each one of these exercises; maybe I'll leave that to another post.
However, I want to share a bit some thoughts on how to engage, on how we can split that into 3 steps discussed ahead.</p>
<h2 id="engaging">Engaging</h2>
<p>Making a talk around the "engage" theme made me reflect for a while. Well, that is always a good thing :)</p>
<p>I looked back and thought a bit about some activites I got involved in one team.</p>
<p>First, what do we mean by engage?
We may think on it as a way of reaching out someone in order to make something happen. However, this may be quite a restricted vision of it.
Of course engaging has a goal but we want that to become a shared goal, something compelling, that touches our heart and soul. That is like the fuel to ignite something bigger.
There are many different ways of engaging and pursuing that goal, that we'll see ahead.</p>
<p>During my talk, I tried to make a connection between a well-known good pattern for writing test automation code "3 A's" (Arrange, Act, Assert) and one that we could try applying in our teams: <strong>Acknowledge, Act, Assess</strong>.</p>
<h3 id="acklowledge">Acklowledge</h3>
<p>Acknowledge is about understanding where we stand, what are our blockers for achieving higher quality in the products we're making, deploying, and supporting.</p>
<p>How are we turning ideas into value and is that value being cared for on production?
The best way is to engage to initiate a discussion and a brainstorming about the whole software lifecycle.</p>
<p>Who is involved, when, how, and how deep? What is the flow of information and processes? Where is the knowledge (tricky question)? What are the artifacts we're making and for what purpose? Who is getting blocked, how and when?
Drawing our overall process and pipeline can be quite useful and insightful; doing it together brings additional clarification and visibility about some main blockers right away.</p>
<p>Visualizing some aspects of how we are working and having the time to reflect is key. Doing that as a team is essential.
This exercise was detailed on a <a href="/post/improving-testing-through-testing-debt-quadrants">previous post</a>.</p>
<p>In the end, we have a set of actions, hopefuly prioritized.</p>
<h3 id="act">Act</h3>
<p>Act is about making sure those actions get done. How we get them done is actually something interesting.
We can act by doing, but we can act by exemplifying, act by spreading the love about a certain topic (e.g., unit testing), act by increasing knowledge or bring awareness about something.</p>
<p>But one way of acting, that for me is one of the most interesting ones, is act by <strong>engaging to give opportunities so people can shine</strong>.
We had a developer within the team that wanted to make a shift to test automation; we knew it, so this colleague was given the opportunity to address one key concern we had related to performance. This person, with the help of the team, was help to get an environment up tracking performance on a continuous base, helping out the whole team.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-2021-working-with-teams-to-improve-quality/performance.jpg" alt="performance testing chart" loading="lazy"></p>
<h3 id="assess">Assess</h3>
<p>Every thing we do needs continuously to be improved. Sofware and quality are not static. Teams are not static. The world isn't static.
Therefore we need to always keep reflecting about what we're doing and think on ways o improve it further. Sometimes, we may even have to give a step back and discard some technique, process, tool, artifact, whatever.</p>
<p>In this team we worked, from continuous discussions we had it was clear that we need to improve collaboration and shared understanding. Therefore, we tried out ensemble programming (also known as "mob" programming). And that was a win for us. But we also learned we needed to fine tune it and that's what we did and keep doing. We have to adapt practices, because people are not APIs; we are humans.</p>
<h2 id="so-can-we-improve-quality">So can we improve quality?</h2>
<p>In my experience, quality can always be improved.
There's no specific technique, process or tool to achieve it.
Problems usually are related to lack of knowledge, lack of true collaboration, and lack of proper tooling.</p>
<p>We need to start by acknowleding where we stand, to identify the items we need to address that can benefit the team as a while, and prioritze them.</p>
<p>We then need to actually do something about those; we need to act, act by doing and act by exemplifying, and also act by empowering others. Finally but not least, we need to always evaluate our progress, because we need to be able to adapt and correct our route as soon as possible.</p>
<p>Improving quality is not a straight road. It's a route, where may need to turn at different places, at different moments. Sometimes we may event have to stop or take a step back.</p>
<p>We need to seed knowledge and care it by collaborating within the team. Because knowledge grows better when you care for it; whenever the team cares for it. And as with any seed, if you can depict, use or even build tools that can help you along the way to be more productive.. the better!</p>
<p>You may be planting knowledge. But your crop will be... quality improvement!</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-2021-working-with-teams-to-improve-quality/three_as.jpg" alt="acknowledge, act, assess" loading="lazy"></p>
<h2 id="questions">Questions</h2>
<p>During my presentation, several questions came. I think they are quite interesting and I wanted to share with you my perspective that may help others out there, or even make you reflect a bit.</p>
<h3 id="you-shared-some-experiences-and-exercises-that-people-can-try-in-order-to-improve-how-they-work-what-was-the-one-that-had-the-most-impact-did-any-of-them-surprise-you">You shared some experiences and exercises that people can try in order to improve how they work. What was the one that had the most impact? Did any of them surprise you?</h3>
<p>All the exercises made with and within the team had impacts.
Advocating continuously about unit testing, leveraged by real example-based workshops made quite a difference on improving the number of unit tests and related coverage, and the <strong>confidence</strong>. However, changing the way people <strong>work</strong>/collaborate by adopting ensemble programming sessions increased not only confidence but also shared understanding (i.e., <strong>knowledge</strong>).
Ensemble sessions were a surprise because they represented quite a change from the way we were working before, mostly based on solo programing + pull requests + testing a bit aside. I thought it would be quite hard but it wasn't and people got satisfied.
As mentioned during the talk, we use a mix: ensemble programming, pair programming (+ a tester), and solo programming. Most of the work gets done through these ensemble sessions though.</p>
<h2 id="what-do-you-mean-by-untrustworthy-things-to-test-could-you-give-an-example">What do you mean by "untrustworthy things to test?" Could you give an example?</h2>
<p>In the testing debt quadrants, one of the quadrants is aimed for those things that we cannot really trust.
And why wouldn't we trust? Because we may lack information
Imagine a product that integrates somehow with another product, that you usually don't test. That integration can be leveraged on a API but it can also make use of some shared CSS for example. You may also not understand fully the use cases of the external product, so certain parts of how that integration becomes used is unknown. Therefore, thinking on a more end-to-end user journey, bits of your product may be used in ways that you didn't foresee.
Other examples could include the fact that you don't have a representative test data, or maybe your data is not big enough.</p>
<h2 id="how-could-we-improve-qadev-relationship">How could we improve QA/Dev relationship?</h2>
<h2 id="what-would-you-recommendadvise-to-testers-on-how-they-practice-effective-collaboration--engagement-with-the-dev-team-when-dev-team-is-actually-in-the-middle-of-building-system">What would you recommend/advise to testers on how they practice effective collaboration / engagement with the dev team when dev team is actually in the middle of building system?</h2>
<p>That's an excellent and recurring question but we can generalize it for any other relationship within the team.
Between devs&#x3C;>testers, first we need to talk a bit about our blockers... as a whole, and for each one of us.
Doing some exercises together is quite helpful because it makes people talk about a certain topic and start having a shared understanding on the concerns and challenges. At that time, people we'll start offering to address some of these challenges, no matter the role they have.
We hear a lot about "quality is a shared responsibility"; that's true but getting to the point where people understand it requires working on it daily, on every aspect that we do. Therefore it's important to start having a more true ownership about what we build and how we build, deploy, and operate it. We should avoid having conversations where a certain quality aspect is assigned to a specific person.
We want devs to excel in what they do and how they do, removing them from the burden of dealing with major issues; testers can help making that happen.
Whenever a dev is working on a story (well, it's only that dev but I'm simplifying it), that code gets tested later on and then is sent back a few times... well, we can take that moment to say "well, instead of sending this back all over again, let me try help out next time and as soon as you start working on the story, ping me and I'll join you to help clarify things from the start".
The whole idea is to make the tester not the "bad news messenger" but instead "the window opener", that will show what behind it and agree about the way to move forward on the world behind that window.</p>
<h2 id="how-do-you-convince-devs-to-get-testers-into-such-pair-programming-sessions-i-often-hear-things-like-testers-slow-us-down-during-pair-sessionsi-dont-have-time-to-explain-stuff-to-the-tester">How do you convince devs to get testers into such pair programming sessions? I often hear things like "testers slow us down during pair sessions...I don't have time to explain stuff to the tester."</h2>
<p>Testers are invited to these sessions. Most times they are present, some times they may not be. Testers engagement also depends on what is being done: if it's a more coding related aspect, maybe the tester does something else. The idea is to have a kind of open format, where testers can join... and if they are not there, devs can easily ping them to ask for their insights. Having a open stage is a good thing.
To get testers into these sessions in our case was not that hard. Some time ago we had concluded that we needed to improve dev&#x3C;>testers collaboration, even while the team was working mainly using PRs (pull requests). Back then, we worked with devs to make them more open to invite testers to help out giving feedback early. This took a bit of time to get it rolling but after some time devs appreciated becaused they saw less stories being sent back for fixing. Moving from that point where some collaboration was already going on, having testers in the ensemble sessions was natural; however, as mentioned earlier, they don't need to stick all the time.</p>
<h2 id="how-hard-was-it-to-form-the-inital-groups-of-testers-and-developers-did-all-go-smooth-or-were-there-some-starting-probems-and-if-so-how-did-you-solve-them">How hard was it to form the inital groups of testers and developers? Did all go smooth or were there some starting probems (and if so how did you solve them)?</h2>
<p>It went smooth AFAIK. These groups are not fixed. We tried to have some balance within the groups, so we don't have all experts in one group and all juniors in a separate one.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/eurostar-2021-working-with-teams-to-improve-quality/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Some perspectives on quality]]></title>
            <link>https://www.sergiofreire.com/post/some-perspectives-on-quality</link>
            <guid>https://www.sergiofreire.com/post/some-perspectives-on-quality</guid>
            <pubDate>Mon, 31 May 2021 12:30:00 GMT</pubDate>
            <description><![CDATA[<p>A couple of weeks ago, I participated in an assessment with 7 distinct teams to understand a bit more about their overall development process, including their approach on testing and quality.
We tried to uncover a bunch of different things, related to collaboration, practices, tooling, CI/CD, testing, etc.</p>
<p>One of the more subjective questions raised was: <strong>What does quality mean to you and your team?</strong></p>
<p>We wanted to understand the team's perspective on quality and cross-relate it with the practices followed within the team and the overall status of the underlying project.</p>
<h2 id="the-answers-to-what-does-quality-mean-to-you-and-your-team">The answers to: What does quality mean to you and your team?</h2>
<p>In order to understand the answers, we need to provide a bit more context about each team and the software product being handled by them.</p>
<h3 id="team-1">Team 1</h3>
<p>This product can be described as providing a very basic UI for processing an input and producing a well-defined output; it's a small team working in a product that exists for quite a while.</p>
<blockquote>
<p>Security on the usage of the software; working without problems; always works in the same way and produces the same output</p>
</blockquote>
<p>The team has been addressing some security issues that have been found (internally and through crowd-sourced security resources).
The perspective on quality is a match with the ongoing security concerns and also with correctness.</p>
<h3 id="team-2">Team 2</h3>
<p>This is not actually a product; it's a more tactical team (with shared resources) that helps with small projects that can serve multiple teams or that can start some module from scratch, later on to be incorporated in another product.</p>
<blockquote>
<p>Conformity level between initial requirements and what the team delivered</p>
</blockquote>
<p>For this team, it makes sense to provide such an answer as the team works with specifications, addresses these in a short time frame, does the handover, and moves on.</p>
<h3 id="team-3">Team 3</h3>
<p>This is a medium team working on a product that provides a web UI. The product is SaaS based and has many concurrent users.
The team embraces some DevOps practices, handles support, deployment and operations.</p>
<blockquote>
<p>Absence of bugs (mostly bugs from support feeds) and to performance issues; (we're) mostly focused on customer feedback from support; How the team is performing/working</p>
</blockquote>
<p>The feedback about quality is essentially around feedback coming from support, which may result in bug reports on feature requests. Besides, as it is a SaaS product serving large thousands of customers in multiple locations, performance is a concern, especially because the team is responsible for deploying to production and maintaining it.
This kind of end-to-end perspective is interesting: on the left side, listening to customers... and on the right side, caring for production, using monitoring and observability (to an extent).</p>
<h3 id="team-4">Team 4</h3>
<p>This small, young team works on an innovative product. It's a product where user experience and UI are important, that runs on local machines, supporting multiple OSes (operating systems), and receives frequent updates.</p>
<blockquote>
<p>Easy to use (without requiring documentation) and the app is working without issues (no crashes or bugs)</p>
</blockquote>
<p>This perspective on quality matches the product context as it has to be easy to use and it shall "behave fine" in user machines, without crashes. Knowing that users will use different versions of OSes, with all sorts of configurations, is challenging for features and stability.</p>
<h3 id="team-5">Team 5</h3>
<p>This medium-sized team works on a product that exists for several years. It provides a basic UI but the core features are related to data processing, whose size and conditions are unknown upfront. The product is deployed in servers, having different configuration/infrastructure scenarios. Feature releases are done every month, while bugfix releases are pushed more frequently.
The team handles support issues often.</p>
<blockquote>
<p>Be able to make a release without bugs and without performance issues</p>
</blockquote>
<p>The feedback on quality meaning aligns with the core concerns of the product. Besides correctness, performance is one of the main concerns due to data aspects (e.g. size, format) and also to the fact that data handling/processing is done in customer infrastructure, that may not be properly sized.</p>
<h3 id="team-6">Team 6</h3>
<p>This is a medium/large team working in a product that provides a UI and that is installed locally. It has to support diverse customer scenarios. It is used by millions of users and also in numerous ways.
The product has a long tail of feature requests and bug reports.</p>
<blockquote>
<p>Keep up with customer expectations, concerning features and bugs</p>
</blockquote>
<p>This answer has a reasoning behind it: the high customer interaction with the team through support and other channels. Also, there's a public roadmap that customers track and try to push.</p>
<h3 id="team-7">Team 7</h3>
<p>This team works in a product that is actually a component used by other products/teams.
It has no UI; it received some data, processes it, and then generates an output.
It's a very small team.</p>
<blockquote>
<p>Generate output without errors and in a fast way</p>
</blockquote>
<p>The concern of this team is around delivering always the same output, and do it as fast as possible. Thus, correctness and performance arise as the main concerns.</p>
<h2 id="in-sum">In sum</h2>
<p>We can see that the actual meaning of quality is distinct from team to team.</p>
<p>This is due to several factors:</p>
<ul>
<li>team context (e.g. maturity, skills, collaboration)</li>
<li>product context (e.g. what is important for continuous product success?</li>
<li>current context (i.e. what's important right now? What are we currently struggling with?)</li>
</ul>
<p>Answers may be a bit vague (e.g. "without bugs/errors", "keep up with customer expectations") in the sense that they hide multiple quality criteria, that we would need to decompose further to have more clear insights.
Some of these become a bit more clear when we uncover the testing being done, or that the team wished to have.</p>
<p>The quest for uncovering quality proceeds :)</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/some-perspectives-on-quality/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Quality in simple words]]></title>
            <link>https://www.sergiofreire.com/post/quality-in-simple-words</link>
            <guid>https://www.sergiofreire.com/post/quality-in-simple-words</guid>
            <pubDate>Thu, 27 May 2021 17:30:00 GMT</pubDate>
            <description><![CDATA[<p>What is quality?! This is a recurring topic and ongoing discussion.</p>
<p>Maybe it’s an emotion or it perhaps a comparison?</p>
<p>Is there a simple way to define it?</p>
<p>Before answering this question, let me start by sharing a story.</p>
<h2 id="a-real-story-about-someone-that-got-fired-and-didnt-care-that-much-there-was-a-reason-for-it">A real story about someone that got fired and didn't care that much (there was a reason for it!)</h2>
<p>I always remember this story from 2007, when I was working in East Timor.</p>
<p>I was delivering a product for one of the biggest companies in East Timor. In fact, I was doing the full hardware and software installation, testing, talking with customer, well... a bit of all. At that time, we had to be able to solve as much as possible and deal with all the possible and imaginary constraints :)
I loved East Timor and I wish I may come back there once again, as an explorer!</p>
<p><img src="https://www.sergiofreire.com/assets/blog/quality-in-simple-words/timor2.jpeg" alt="East Timor beach" loading="lazy"></p>
<p>But the story is about this person that got fired because he had done a mistake. Actually, the boss wastrying to discuss with him about what happened and see his reaction to decide how to proceed.</p>
<p>That person wasn't concerned at all about losing the job. East Timor was a poor country and there weren't much job offers available. But that folk wasn't worried at all about getting unemployed.
That for me was totally strange. "How can he survive without a job?" - I asked myself.</p>
<p>And then someone explained me the local context. Many people didn't actually need money. Why? Because they exchange things between them, like food supplies. Electricity and water were not a real concern back then.
Thus, the value I assign to money was totally different from the value that person assigned to money, which was close to 0.
And that changes your whole perspective about things, about life and the things you do with it.
So, finding what matters will influence everything you'll do.</p>
<p>We may use this story reflect a bit about quality, value and testing.
I've previously written about the relation between them using <a href="/post/the-quality-ice-cream-truck">The Quality Ice Cream Truck model</a>.</p>
<h2 id="what-is-quality">What is quality?</h2>
<blockquote>
<p>Quality is value to some person
-- Jerry Weinberg</p>
</blockquote>
<p>Quality is value to some person (quote from Jerry Weinberg, who has also a nice <a href="http://secretsofconsulting.blogspot.com/2016/08/what-is-quality-why-is-it-important.html?platform=hootsuite">post on this topic</a>). Well, in other words it means that quality is subjective. It depends on the eyes of the beholder.
A couple of weeks ago I was having a conversation with my wife about values in life and she was counterarguing with me "well, those are not values... those are feelings".</p>
<p>That made me think for a while. I do think values and feelings are different things but they do have a subjective root.</p>
<p>Thinking on quality, we were saying that quality is value to some person or stakeholder... and indeed we’re talking about feelings, what makes those stakeholders satisfied or happy. Even if it's happy enough!</p>
<h2 id="quality-defined-as-simple-as-possible">Quality, defined as simple as possible</h2>
<p>So, putting it in simple words, <strong>quality is about making people happy</strong>, no matter if we’re talking about the internal product team, business, organization, or the end users.</p>
<p><strong>Starting with end users, they become happy if:</strong></p>
<ul>
<li>It addresses their needs</li>
<li>It addresses their expectactions</li>
</ul>
<p><em>And what do we exactly mean by that? What are those expectactions? Does the system perform what it says? Maybe there are performance expectactions and other we should care about?</em></p>
<ul>
<li>End users also become happy if they have  seamless and simplified experiences</li>
</ul>
<p><strong>The team becomes happy if:</strong></p>
<ul>
<li>Changes can be made safely (e.g.through good "testability")</li>
<li>Changes can be easily made ("maintainability")</li>
<li>One can understand the architecture</li>
<li>Software can be operated easily (i.e. "operability")</li>
<li>One can understand what is happening (e.g. "observability", "monitoribility")</li>
<li>It provides the means for RCA (Root Cause Analysis)</li>
</ul>
<p><strong>Organization becomes happy with:</strong></p>
<ul>
<li>Low costs with support and maintenance</li>
<li>Low headaches with customers</li>
<li>Low exposure in social media due to problems</li>
<li>Ability to innovate fast</li>
<li>Ability to overcome problems fast</li>
</ul>
<p>All these people will approach quality from different angles, which change with time.</p>
<h2 id="quality-and-happinness-are-multidimensional">Quality and happinness are multidimensional</h2>
<p>Quality is also multidimensional.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/quality-in-simple-words/mutidimensional.jpg" alt="Multidimensional portal" loading="lazy"></p>
<p>Using a balance as an analogy, each one of us, uses different weights for different quality criteria... therefore, the overall quality as measured by that balance, will be different for every single person. <strong>The variables, along with their weights, that make a certain person happy are different from person to person.</strong></p>
<p>Besides, quality is not static and may easily get outdated.</p>
<p>Imagine an Instagram profile with few posts; people would say it has "low quality" and its interest decreases with time.</p>
<p>That’s why we need to provide value consistently, or else our product will get outdated &#x26; forgotten. That implies that we deliver value and also that we understand if we’re actually delivering value. In other words, we need to understand as much as possible about our users and how they are, or not, using the product.</p>
<p>We need to find ways of measuring value. Hmmm that could be a whole new topic but we can try having a simple approach instead of a perfect, non-feasible one.</p>
<ul>
<li>If a page/feature becomes unused then its value is questionable.</li>
<li>If our users use our product while it’s free but we cannot convert them, well maybe we need to test our value proposition.</li>
</ul>
<h2 id="wrapping-it-up">Wrapping it up</h2>
<p>This simplified definition for quality - what makes people happy - can be a nice way of framing quality and explaining it.
What makes each of us happy is different, in terms of the factors that contribute to it and their weights.</p>
<p>Besides, it changes with time. At a given moment we can be amazed by some website performance but later in time that can have less importance, when compared against the website content itself for example.</p>
<p>And our happinness also depends on getting value consistently (more on that in an upcoming post), or else we lose interest.
Whenever talking about quality in (software/hardware) testing, we're in fact also looking at these aspects.
Is this a rough/simplified definition for quality that works for you? What's yours?</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/quality-in-simple-words/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Robot Framework Browser library architecture]]></title>
            <link>https://www.sergiofreire.com/post/robotframework-browser-architecture</link>
            <guid>https://www.sergiofreire.com/post/robotframework-browser-architecture</guid>
            <pubDate>Mon, 22 Mar 2021 12:00:00 GMT</pubDate>
            <description><![CDATA[<p>Playwright is becoming an important browser automation library and a serious competitor or challenger to Selenium.
As part of my ongoing exploration, I wanted to understand the internals of all the components and protocols involved. In this article, I'll try to deep dive the architecture, at least to a point. I won't go into an extensive explanation of the library itself, its keywords, or comparison with other libraries such as SeleniumLibrary; however, I'll touch some of those topics where it may be relevant.</p>
<p>Before Playwright, Selenium was the main browser automation library and <a href="https://robotframework.org/SeleniumLibrary/">SeleniumLibrary</a> (i.e. <a href="https://pypi.org/project/robotframework-seleniumlibrary/">robotframework-seleniumlibrary</a>) was the Python package you would install for assisting on web-based testing using Robot Framework along with some specific keywords.</p>
<p>Meanwhile, <a href="https://playwright.dev/">Playwright</a> appeared. It provides a completely different approach for automating the browser, making use of asynchronous calls and having access to and capabilities of changing some browser internals.
Just as quick overview, Playwright talks to Chromium-based browsers using the <a href="https://chromedevtools.github.io/devtools-protocol/">Chrome DevTools Protocol (CDP)</a>, allowing you to browse and interact with web elements and also emulate devices, location, handle network events, and much more.</p>
<h2 id="architecture">Architecture</h2>
<p>In order to use the Browser Library, we need to install it as usual using "pip" and then we have to "initialize it" with "rfbrowser init". The latter command will:</p>
<ul>
<li>install the latest version of the <a href="https://www.npmjs.com/package/playwright">playwright</a> NPM package in our Node.js environment</li>
<li>install local browsers, for chromium, webkit, and firefox browser types</li>
</ul>
<p>Then we are ready to use Browser library in our RF tests.</p>
<p>The following diagram gives an overview of the underlying architecture and the flows involved when we automate our browser(s) using this library on top of Robot Framework.</p>
<p>This architecture may seem a bit cumbersome but there's a reason behind it.
When Browser library project was started, there wasn't any Python library available for Playwright. The team decided to make a gRPC wrapper that would interact with a Node.js process where the official JavaScript "playwright" package would be running.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/robotframework-browser-architecture/browser_library_architecture.jpg" alt="Browser library architecture" loading="lazy"></p>
<p>All of these components can run in the same host; eventually, the browser can be on a separate host. However, the Node.js process must run alongside with RF.</p>
<p>Test cases are written as usual in Robot Framework (RF) but now making use of keywords provided by the Browser library.
RF usually runs in Python and Browser library can be <a href="https://marketsquare.github.io/robotframework-browser/Browser.html#Extending%20Browser%20library%20with%20a%20JavaScript%20module">extended using JavaScript</a>.</p>
<p>Browser library, when initialized, launches a Node.js process implementing a <a href="https://grpc.io/docs/what-is-grpc/introduction/">gRPC</a> server that will translate incoming requests to the Node.js <a href="https://github.com/microsoft/playwright">Playwright library</a>. Whenever using keywords of the Browser library, gRPC calls are made to the Node.js server.
Messages are exchanged using Protobuf (i.e. Protocol Buffers) messages, on top of gRPC with a <a href="https://github.com/MarketSquare/robotframework-browser/blob/ae5e14fc673ef113ecfedf036f2ddcb04f605a0b/protobuf/playwright.proto">specific protocol/messages</a>, which in turn works on top of HTTP/2.
The Node.js gRPC server will talk directly to the browsers, using the Playwright API and specific browser protocols, such as CDP (to be discussed on an upcoming blog post).</p>
<p>We could analyze what's going on in a simple RF test scenario that tries to obtain the page title.
For that, we'll use Wireshark and install the <a href="https://github.com/MarketSquare/robotframework-browser/blob/ae5e14fc673ef113ecfedf036f2ddcb04f605a0b/protobuf/playwright.proto">protobuf message specification</a>. We can see the calls between the Browser library, in our Python code, and the Node.js server that acts as a proxy to Playwright.
The following screenshot shows the "Get Title" gRPC call, as a consequence of a "Get Title" keyword in RF, and the respective response.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/robotframework-browser-architecture/wireshark_request.jpg" alt="Get Title gRPC request" loading="lazy"></p>
<p><img src="https://www.sergiofreire.com/assets/blog/robotframework-browser-architecture/wireshark_response.jpg" alt="Get Title gRPC response" loading="lazy"></p>
<h3 id="running-tests-in-parallel">Running tests in parallel</h3>
<p>It's possible to use <a href="https://pabot.org/">pabot</a> to run Browser enabled tests in parallel.
In this case, a browser instance will be created per each pabot process.
Enhancements are being discussed to provide additional speed improvements and reuse browsers more efficiently. This may affect the previous architecture.</p>
<h2 id="comparing-selenium-vs-browser-libraries-syntax">Comparing Selenium vs Browser libraries syntax</h2>
<p>First, and to understand better what lies under the syntax, concepts are different, and Browser library can be said to provide finer control over the browser.
<a href="https://twitter.com/AaltoTatu">Tatu Aalto</a>, along with <a href="https://twitter.com/mikkorpela">Mikko Korpela</a>, <a href="https://twitter.com/Xgylix">giKerkko Pelttari</a>, <a href="https://twitter.com/0x52R">René Rohner</a>, explained this during RoboCon 2021.
<img src="https://www.sergiofreire.com/assets/blog/robotframework-browser-architecture/browser_vs_seleniumlibrary.jpg" alt="Browser vs SeleniumLibrary&#x22;" loading="lazy"></p>
<p>In SeleniumLibrary, all happens on top of a browser instance. We create a new browser using the "Open Browser" keyword, which in fact launches a new browser and thus takes some time and requires considerable resources. If we want to have a clean session, we close the current browser instance and have to crete a new one.
On the other hand, in Browser library there are 3 layers: <a href="https://marketsquare.github.io/robotframework-browser/Browser.html#Browser%2C%20Context%20and%20Page">Browser, Context, Page</a>.</p>
<p>Our browser instance is reused as much as possible, and it makes sense to create it during the init phase. Then we have "Contexts", which are configurable "incognito" like instances: they start in a clean state and are fast to create/delete. "Pages" are our typical browser tabs.
If we want to have a clean session for our test, we can create a specific context for it. If we want to reuse a session between multiple tests, then we reuse the context and eventually stick its creation to the Suite Setup.</p>
<p>There's another interesting difference: in SeleniumLibrary we need to open and close browser and pages explicitly. Browser library handles some of this automatically: if a context or a page is created in the Test Setup/Suite Setup, then it will be automatically closed in the respective Test Teardown/Suite Teardown; the browser will be automatically closed at the end of test execution. There's a setting that allows us to <a href="https://marketsquare.github.io/robotframework-browser/Browser.html#AutoClosingLevel">finetune the behaviour</a> of this whenever importing the library.</p>
<p>What concerns the syntax itself, at first sight you may not see a big difference if you have a layer of abstraction. In the following example, the .robot file with the test case is mostly the same. The only subtle change in this case is that "Close Browser" was replaced by "Close Page", even though we don't actually need to use it as mentioned earlier :)</p>
<div class="remark-highlight"><pre class="language-robot"><code class="language-robot"><span class="token settings section"><span class="token section-header keyword">*** Settings ***</span>
<span class="token property">Documentation</span>     <span class="token documentation string">A test suite with a single test for valid login.</span>
<span class="token documentation string">...</span>
<span class="token documentation string">...               This test has a workflow that is created using keywords in</span>
<span class="token documentation string">...               the imported resource file.</span>
<span class="token property">Resource</span>          resource.robot
</span>
<span class="token test-cases section"><span class="token section-header keyword">*** Test Cases ***</span>
<span class="token test-name function">Valid Login</span>
    <span class="token tag"><span class="token punctuation">[</span>Tags<span class="token punctuation">]</span></span>  ROB-11  UI
    <span class="token property">Open Browser To Login Page</span>
    <span class="token property">Input Username</span>    demo
    <span class="token property">Input Password</span>    mode
    <span class="token property">Submit Credentials</span>
    <span class="token property">Welcome Page Should Be Open</span>
    <span class="token tag"><span class="token punctuation">[</span>Teardown<span class="token punctuation">]</span></span>    Close Browser</span>
</code></pre></div>
<div class="remark-highlight"><pre class="language-robot"><code class="language-robot"><span class="token settings section"><span class="token section-header keyword">*** Settings ***</span>
<span class="token property">Documentation</span>     <span class="token documentation string">A test suite with a single test for valid login.</span>
<span class="token documentation string">...</span>
<span class="token documentation string">...               This test has a workflow that is created using keywords in</span>
<span class="token documentation string">...               the imported resource file.</span>
<span class="token property">Resource</span>          resource.robot
</span>
<span class="token test-cases section"><span class="token section-header keyword">*** Test Cases ***</span>
<span class="token test-name function">Valid Login</span>
    <span class="token tag"><span class="token punctuation">[</span>Tags<span class="token punctuation">]</span></span>  ROB-11  UI
    <span class="token property">Open Browser To Login Page</span>
    <span class="token property">Input Username</span>    demo
    <span class="token property">Input Password</span>    mode
    <span class="token property">Submit Credentials</span>
    <span class="token property">Welcome Page Should Be Open</span>
    <span class="token comment"># [Teardown]    Close Page</span></span>
</code></pre></div>
<p>The actual differences are hidden in the implementation details, in a file named "resources.robot".</p>
<p>Using SeleniumLibrary,</p>
<div class="remark-highlight"><pre class="language-robot"><code class="language-robot"><span class="token keywords section"><span class="token section-header keyword">*** Keywords ***</span>
<span class="token keyword-name function">Open Browser To Login Page</span>
    <span class="token property">Open Browser</span>    <span class="token variable"><span class="token punctuation">${</span>LOGIN URL<span class="token punctuation">}</span></span>    <span class="token variable"><span class="token punctuation">${</span>BROWSER<span class="token punctuation">}</span></span>
    <span class="token property">Maximize Browser Window</span>
    <span class="token property">Set Selenium Speed</span>    <span class="token variable"><span class="token punctuation">${</span>DELAY<span class="token punctuation">}</span></span>
    <span class="token property">Login Page Should Be Open</span>

<span class="token keyword-name function">Login Page Should Be Open</span>
    <span class="token property">Title Should Be</span>    Login Page

<span class="token keyword-name function">Go To Login Page</span>
    <span class="token property">Go To</span>    <span class="token variable"><span class="token punctuation">${</span>LOGIN URL<span class="token punctuation">}</span></span>
    <span class="token property">Login Page Should Be Open</span>

<span class="token keyword-name function">Input Username</span>
    <span class="token tag"><span class="token punctuation">[</span>Arguments<span class="token punctuation">]</span></span>    <span class="token variable"><span class="token punctuation">${</span>username<span class="token punctuation">}</span></span>
    <span class="token property">Input Text</span>    username_field    <span class="token variable"><span class="token punctuation">${</span>username<span class="token punctuation">}</span></span>

<span class="token keyword-name function">Input Password</span>
    <span class="token tag"><span class="token punctuation">[</span>Arguments<span class="token punctuation">]</span></span>    <span class="token variable"><span class="token punctuation">${</span>password<span class="token punctuation">}</span></span>
    <span class="token property">Input Text</span>    password_field    <span class="token variable"><span class="token punctuation">${</span>password<span class="token punctuation">}</span></span>

<span class="token keyword-name function">Submit Credentials</span>
    <span class="token property">Click Button</span>    login_button

<span class="token keyword-name function">Welcome Page Should Be Open</span>
    <span class="token property">Location Should Be</span>    <span class="token variable"><span class="token punctuation">${</span>WELCOME URL<span class="token punctuation">}</span></span>
    <span class="token property">Title Should Be</span>    Welcome Page</span>
</code></pre></div>
<p>While on Browser library,</p>
<div class="remark-highlight"><pre class="language-robot"><code class="language-robot"><span class="token keywords section"><span class="token section-header keyword">*** Keywords ***</span>
<span class="token keyword-name function">Open Browser To Login Page</span>
    <span class="token property">New Page</span>    <span class="token variable"><span class="token punctuation">${</span>LOGIN URL<span class="token punctuation">}</span></span>
    <span class="token property">Login Page Should Be Open</span>

<span class="token keyword-name function">Login Page Should Be Open</span>
    <span class="token property">Get Title</span>    ==    Login Page

<span class="token keyword-name function">Go To Login Page</span>
    <span class="token property">Go To</span>    <span class="token variable"><span class="token punctuation">${</span>LOGIN URL<span class="token punctuation">}</span></span>
    <span class="token property">Login Page Should Be Open</span>

<span class="token keyword-name function">Input Username</span>
    <span class="token tag"><span class="token punctuation">[</span>Arguments<span class="token punctuation">]</span></span>    <span class="token variable"><span class="token punctuation">${</span>username<span class="token punctuation">}</span></span>
    <span class="token property">Type Text</span>    id=username_field    <span class="token variable"><span class="token punctuation">${</span>username<span class="token punctuation">}</span></span>

<span class="token keyword-name function">Input Password</span>
    <span class="token tag"><span class="token punctuation">[</span>Arguments<span class="token punctuation">]</span></span>    <span class="token variable"><span class="token punctuation">${</span>password<span class="token punctuation">}</span></span>
    <span class="token property">Type Text</span>    id=password_field    <span class="token variable"><span class="token punctuation">${</span>password<span class="token punctuation">}</span></span>

<span class="token keyword-name function">Submit Credentials</span>
    <span class="token property">Click</span>    id=login_button

<span class="token keyword-name function">Welcome Page Should Be Open</span>
    <span class="token property">Get Url</span>    ==    <span class="token variable"><span class="token punctuation">${</span>WELCOME URL<span class="token punctuation">}</span></span>
    <span class="token property">Get Title</span>    ==    Welcome Page</span>
</code></pre></div>
<p>Keywords and semantics are slightly different in this case; we can say that it's easy to understand Browser library syntax and adopt it.
My recommendation would be to have a look at the <a href="https://marketsquare.github.io/robotframework-browser/Browser.html">official documentation</a> as it provides detailed information. Of course, Browser is different from SeleniumLibrary, and you'll find unique keywords on Browser that have no counterparts in SeleniumLibrary. One of those examples is <a href="https://marketsquare.github.io/robotframework-browser/Browser.html#Promise%20To">"Promise To"</a>, one of the keywords that allows you to implement Promises (i.e. keywords that run on the background and that you can wait for them later on).</p>
<p>One difference that pops up, is that while SeleniumLibrary provides specific keywords for assertions (e.g. "Title Should Be"), while the Browser library approach is different: there are <a href="https://marketsquare.github.io/robotframework-browser/Browser.html#Assertions">assertion operators</a> (e.g. "==", "!=") that can be used for some keywords as long as they accept these as arguments (e.g. "Get Title  ==  Welcome Page").
Meanwhile, the assertion engine has been split to a separate library: <a href="https://github.com/MarketSquare/AssertionEngine">robotframework-assertion-engine</a>.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Robot Framework's Browser library is an interesting library that brings Playwright browser automation capabilities to RF users.
This project uses a mix of technologies under the hood; that leads to additional requirements, including having a Node.js environment.
I found out this mix interesting, as it involves different languages, frameworks, and protocols... and a workaround (using gRPC to communicate to a Node.js process) to access Playwright automation library.</p>
<p>The current architecture, as of v4.0.x of "robotframework-browser" package, could be eventually simplified in order to use the <a href="https://pypi.org/project/playwright/">"playwright" Python package</a> directly. As Kerkko Pelttari mentions, the current architecture allows to have greater control over the Playwright features available in the Browser library implementation. Depending on an upstream wrapper package for Python would add another dependency that could bring increased delays for releasing new versions; "wrapper libraries" have some lag from the main project (i.e. "playwright" NPM package in this case).  Besides, changing the current architecture to use "playwright" package would also require significant refactoring. Anyway, we never know what the future brings, so it's always good to go over the release notes and the official documentation to be up to date.
Playwright provides visible speed gains because it doesn't require starting new browser instances; we can simply create new contexts... and that is fast. In a simple scenario, I reduced the overall elapsed time by 50%.</p>
<p>If you can, I would recommend watching the Browser library talks that were part of <a href="https://robocon.io/">RoboCon 2021</a>, to learn more from the Browser library authors themselves :)</p>
<h2 id="learn-more">Learn more</h2>
<ul>
<li><a href="https://robotframework-browser.org/">RF Browser library</a></li>
<li><a href="https://playwright.dev/">Playwright documentation and API</a></li>
<li><a href="https://github.com/microsoft/playwright">Playwright repo</a></li>
<li><a href="https://robotframework.org/SeleniumLibrary/SeleniumLibrary.html">RF SeleniumLibrary documentation</a></li>
<li><a href="https://grpc.io/blog/wireshark/">Analyzing gRPC messages using Wireshark</a></li>
<li><a href="https://github.com/MarketSquare/robotframework-browser/blob/ae5e14fc673ef113ecfedf036f2ddcb04f605a0b/protobuf/playwright.proto">protbuf specification used between Browser library&#x3C;=>Node.js server</a></li>
<li><a href="https://robotframework-slack-invite.herokuapp.com/">"browser" channel on RF Slack</a></li>
</ul>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/robotframework-browser-architecture/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Implementing a review process for EuroSTAR testing conference]]></title>
            <link>https://www.sergiofreire.com/post/eurostar-testing-conference-review-process</link>
            <guid>https://www.sergiofreire.com/post/eurostar-testing-conference-review-process</guid>
            <pubDate>Mon, 01 Mar 2021 14:00:00 GMT</pubDate>
            <description><![CDATA[<p>I was invited to review some submissions for EuroSTAR 2021 testing conference.
I had never done it before, and even if time is scarce I understand that this is important for many who submit during the Call For Speakers; it's also a learning experience for me.
But how have I tackled this task? How can I make sure I'm fair enough to everyone?
Can I test my review process somehow?
This is the story of how I tested my review process. Let's check it out.</p>
<h2 id="background">Background</h2>
<p>Before starting, it's important to understand the conference context.
<a href="https://conference.eurostarsoftwaretesting.com/call-for-submissions/">EuroSTAR 2021 theme</a> is "Engage". Engagement is a broad topic. It's about involvement, commitment, challenging, seeking understanding.
We deep dive problems and challenges, we embrace them and we overcome them, no matter if they live within us or in our teams.
Engaging is, in my perspective, about giving a step forward.</p>
<h2 id="infrastructure-and-supporting-assets">Infrastructure and supporting assets</h2>
<p>The review was done using an online tool where we can easily jump between submissions. The tool itself has an interesting and scary bug that may mislead you to think that your already reviewed submissions/scores are lost... however, I found out that it is just a UI glitch :) a forced browser refresh solved the issue.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-testing-conference-review-process/online_tool.png" alt="online tool for reviewing submissions" loading="lazy"></p>
<p>To assist me as a reviewer, I had scoring instructions that I read before starting reviewing the submissions. These instructions were always open in one of my screens.</p>
<h2 id="criteria">Criteria</h2>
<p>There were 5 main criteria for me to score:</p>
<ul>
<li>engaging</li>
<li>new ideas</li>
<li>scope</li>
<li>relevant to theme</li>
<li>overall feeling</li>
</ul>
<p>I felt a bit of doubts on "engaging" vs "relevant to theme", as the theme is "engaging" and thus some overlap exists. I used the scoring instructions several times, to decide how to score each one of them. Nevertheless, I would prefer a more clear separation between them as they're not independent variables.
The "scope" criterion also left me some doubts, as we'll see ahead.
Finally, there was an open text field "comments" where we could leave a qualitative assessment.</p>
<h2 id="how-i-did-it">How I did it?</h2>
<h3 id="day-1">Day 1</h3>
<p>I had a setup a quiet room (i.e. my office), so I could read and think carefully.
I went over each one of the submissions and scored them; I skipped the "comments" section on purpose so I could not loose much time with them in the first iteration but also to try avoid a bit of bias.
Whenever scoring, I regularly looked the scoring instructions if some criteria was not clear to me.
I noticed that I was probably assigning "scope" not the meaning it should, as the scoring values were almost identical. Even so, I decided to look at it more carefully on a second iteration.
Besides leaving the scores on the online tool, I also took notes in a spreadsheet; thus, I would have a backup :) and I could use it for analysis.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-testing-conference-review-process/1st_round.png" alt="1st_round" loading="lazy"></p>
<p>After scoring all submissions, I decided to go over the submissions once again to leave qualitative comments, that I structured in these bullets:</p>
<ul>
<li><strong>First impressions:</strong> Mostly based on the title and takeaways.</li>
<li><strong>Takeaways &#x26; The Day After:</strong> Is it clear what to learn in the end of this session?</li>
<li><strong>Final assessment:</strong> My overall feeling, i.e. a brief summary.</li>
<li><strong>Would I attend:</strong> A yes or no, or eventually a perhaps.</li>
<li><strong>Cliffhanger/invite to discover:</strong> Was there any mystery left? Any invite to learn something intesting during the session?</li>
<li><strong>Suitable for:</strong> beginner, intermediate, advanced.</li>
</ul>
<p>I was curious to understand if I had some bias that would be reflected more towards time. Thus, I traced a chart with the average scored and a trend line.
The result was not shocking. I also made a column with the max score I add assigned in a specific criterion... and I didn't see any relevant change with time.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-testing-conference-review-process/1st_round_chart.png" alt="1st round chart" loading="lazy"></p>
<p>I slept over and I was curious to see what would happen whenever I iterated on this.</p>
<h3 id="day-2">Day 2</h3>
<p>I went over the submissions once again, but in reversed order. Why? To avoid some possible bias related with time and exhaustion.</p>
<p>In terms of criteria, I also did a subtle change. While in the first day I considered "scope" as the criterion for being adequate for the time slot, in the second day I considered more the message clarity because it would be a factor that would help the fit for the available time slot.</p>
<p>Even though overall scoring is a bit lower, the results are not very different from the 1st iteration, where the chart shows also a similar trend line.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-testing-conference-review-process/2nd_round_chart.png" alt="2nd round chart" loading="lazy"></p>
<p>In the spreadsheet, I calculated the absolute difference on the average and on the total score.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-testing-conference-review-process/2nd_round.png" alt="2nd_round" loading="lazy"></p>
<p>I flagged in red suspicious cases, that would affect my confidence:</p>
<ul>
<li>absolute average scoring difference >= 0.5</li>
<li>absolute difference in individual scores > 1</li>
<li>absolute total scoring difference > 2</li>
</ul>
<p>Gladly, only a few reviews appear that I need to have a look at once again.
After going over them (i.e. scoring) in this localized 3rd iteration, I seem to be in my confidence interval except for one submission that remains flagged. Why? Well, because in the total score it had 3 points of difference.
I read that submission all over and scored it once again. In the end, it was once again in my confidence interval.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/eurostar-testing-conference-review-process/4th_round.png" alt="4th round" loading="lazy"></p>
<p>Remember the "Comments" section? Well, I decided to go over it and look at "would I attend" bullet and compare it with the "overall feeling" criterion, that implictly should have that reflected. I notice that my decision to attend was correlated with the "overall feeling" criterion but it was not a consequence of it.</p>
<h2 id="conclusions-and-final-thoughts">Conclusions and final thoughts</h2>
<h3 id="on-the-process">On the process</h3>
<p>I had quite a few doubts at start, especially this one: am I going to be fair? But also, I had doubts about how I would perform in this task, my biases, etc.</p>
<p>After looking at the results, my first iteration was trustworthy.
I scored the submissions a bit lower during the second day, when I performed the 2nd, 3rd and 4th iterations.
However, my first iteration was balanced and scores should reflect that.</p>
<p>I don't assign 5 or 0 easily. I need to be quite impressed or reject something completely, which for me was not the case.</p>
<p>Scoring talks can be <em>relatively</em> fast but if you want to implement a process such as this one or your own, you'll spend a lot more time. Even if you don't, if you consider all the points that can affect a single criterion then it will consume more time indeed.</p>
<p>This whole process of revieweing submissions is also a discovery process about how each one of us works and is able of handling tasks. What works for someone, may not work for someone else.</p>
<h3 id="on-submitting-a-talk">On submitting a talk</h3>
<p>Talks touch us in subtle different ways, so we value them differently. It makes sense, from a quality perspective: we assign value accordingly with our own definition of quality.</p>
<p>There are some generic rules that apply though in most cases and questions that you can think of:</p>
<ul>
<li>Having a clear message, not that long not that short is crucial; if we get lost, we tend do discard it rightaway.</li>
<li>Why should we attend? What can we expect from it?</li>
<li>How will that talk help the attendee?</li>
<li>How does that talk align with conference?</li>
<li>Is it presenting something new, or your own learnings and challenges?</li>
</ul>
<p>I've done a few submissions on the past and I have been unlucky most times. Yes, true. Therefore, I still have much to learn in this matter. I remember when I did a scientific submission for a Usenix conference few years ago, and even though it was a different audience, I had to iterate on the paper a couple of times before it was accepted and I was invited to present.
Therefore, as everything, we need to iterate and learn.</p>
<p>Going over this review process, being on the other side of the fence, made even more clear some points mentioned above.
By understanding the challenges a reviewer faces, when looking at dozens of submissions, I am a bit more aware of the things I need to have in mind for any submissions I may perform in the future.</p>
<p>It was an interesting thing to do and I've learned a bit more on the overall process of submitting (and reviewing) talks for testing conferences.
I also appreciate the effort reviewers and program committees have, no matter what conference we're talking about, because this is a process that requires considerable effort if you want to make it right and fair.</p>
<p><em>Note: the <a href="https://conference.eurostarsoftwaretesting.com/">2021 EuroSTAR Conference</a> is taking place in September this year instead of November and the programme is usually launched at the end of April.</em></p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/eurostar-testing-conference-review-process/cover2.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Shifting Towards Omni Testing: Part 2]]></title>
            <link>https://www.sergiofreire.com/post/shifting-towards-omni-testing-part2</link>
            <guid>https://www.sergiofreire.com/post/shifting-towards-omni-testing-part2</guid>
            <pubDate>Wed, 17 Feb 2021 14:40:00 GMT</pubDate>
            <description><![CDATA[<p><strong>This is part 2 of a series <a href="https://testingindevops.org/shifting-towards-omni-testing-part-2/">originaly written</a> for "Testing in DevOps" community, from Lisa Crispin et al.</strong></p>
<p><em>In <a href="post/shifting-towards-omni-testing-part1">Part 1</a> I've introduced the idea of "Omni Testing" as a term that better expresses the concept of continually testing throughout the DevOps infinite loop of discovery and delivery. In this article I'll talk about shifting testing efforts versuys what Continuous Testing really means and how Omni Testing can be used to better describe how we look at testing nowadays.</em></p>
<p>We all heard about Shift-Left Testing and Shift-Right Testing in recent years. Probably you may have got the idea that it was the thing, that single idea that was going to massively improve your testing. Due to this misconception, for example, Janet Gregory prefers to think about testing as something always happening and <a href="https://janetgregory.ca/shift-left-why-i-dont-like-the-term/">holistic</a> (i.e. considering all parts of the whole and their interconnection).</p>
<p><strong>Let's be clear: you cannot shift something that is always there and happens all the time; however, what you’re focused on at a given moment in time can, and probably should, change. Within a team, testing happens continuously and in parallel, so different people may be performing quite different testing activities and from different angles.</strong></p>
<p>If we have pure waterfall development in mind, testing happens as a phase after implementation. However, in real-life, testing is not constrained anymore. Of course, some teams perform more intensive testing before releasing; in that sense, people may refer to shift-left testing efforts as a way to clarify requirements altogether from the start and introduce more unit kind of tests. Similarly, with DevOps, you may "shift-right" testing efforts to production and use as a feedback loop to product development.</p>
<p>However, testing itself is not shifted-left nor right; testing efforts can shift though. Having "<a href="https://testingindevops.org/tag/agile-testing-quadrants/">Agile Testing Quadrants</a>" (by <a href="https://twitter.com/lisacrispin">Lisa Crispin</a> &#x26; <a href="https://twitter.com/janetgregoryca">Janet Gregory</a>) and previous work from <a href="https://twitter.com/marick">Brian Marick</a> (i.e. <a href="http://www.exampler.com/old-blog/2003/08/22/#agile-testing-project-2">testing matrix</a>), we can see that there are different types of tests, with different goals. As Lisa and Janet point in their book <em>Agile Testing – A Practical Guide for Testers and Agile Teams</em>, risks are an inherent and important part of projects where testing is used as a mitigation strategy.</p>
<p>As you plan each epic, release, or iteration, work with your customer team to understand the business priorities and analyze risks. Use the quadrants to help identify all the different types of testing that will be needed and when they should be performed.</p>
<p>Thus, the type and level of testing performed, where and how it is performed, are dynamic.</p>
<p>Testing is not a specific task assignable to one person and delimited in time; everyone is involved in testing, it happens in different ways, at the same time and continuously; and this leads us to Continuous Testing.</p>
<h2 id="from-continuous-testing-to-omni-testing">From Continuous Testing to Omni Testing</h2>
<p>Continuous Testing has been around for some time; the “Continuous” wording is not new. Unfortunately, it is being pushed by some tool vendors and some people to sell it as a way to continuously run “automated tests”.</p>
<p>Well, first there aren’t automated tests in a strict sense; you have automated checks but testing frameworks having been calling them as such from the start. Second, “continuous” in testing is not related to automated scripts/checks being continuously run or not. Even if it was, it wouldn’t make sense, as in CI “automated tests” are triggered on code commits, pull-requests, merges or due to some predefined schedule.</p>
<p>So what does Continuous Testing mean? I advise you to start by <a href="https://testingindevops.org/what-is-continuous-testing/">this post</a> from Lisa Crispin and complement it with <a href="https://testautomationu.applitools.com/the-whole-team-approach-to-continuous-testing/">this course</a> from <a href="https://twitter.com/lisihocke">Elisabeth Hocke</a>.</p>
<p>We know that automation plays a key role in DevOps and also, though to a minor scale, on Continuous Integration (CI) practices which are closely related to Continuous Testing.</p>
<p>The CI concept was depicted by <a href="https://twitter.com/grady_booch">Grady Booch</a> and largely widespread with <a href="https://ronjeffries.com/xprog/what-is-extreme-programming/">Extreme Programming</a> as one of the 12 core XP practices (<a href="https://twitter.com/KentBeck">Kent Beck</a>, <a href="https://twitter.com/RonJeffries">Ron Jeffries</a>, <a href="https://twitter.com/wardcunningham">Ward Cunningham</a>, et al.</p>
<p>During Continuous Integration, <a href="https://martinfowler.com/articles/continuousIntegration.html#MakeYourBuildSelf-testing">"automated tests"</a> (i.e. automated checks to be more precise: code that runs and performs checks to validate expected behavior) are run. These will help assure, to a point, that code that has been integrated isn’t breaking anything and previous expectations are still met.</p>
<p>As teams grow and code is committed more often, CI provides the means for immediate feedback so the team knows right away when a possible “bug has been added”. It can quickly be fixed before anyone builds on top of it. These automated scripts perform unit and integration tests but can also cover other types of tests such as security tests and more.</p>
<p>As builds are continuously being started by the CI process, these kinds of tests are also constantly being run.</p>
<p>However, “Continuous Testing” is not limited to these “tests”. Lisa Crispin and Janet Gregory put it like this instead:</p>
<blockquote>
<p>Collaborative testing practices that occur continuously, from inception to delivery and beyond, supporting frequent delivery of value for our customers. Testing activities focus on building quality into the product, using fast feedback loops to validate our understanding. The practices strengthen and support the idea of whole team responsibility for quality.
-- Lisa Crispin and Janet Gregory</p>
</blockquote>
<p>In my opinion, this is the real reason behind the "Continuous" wording before "Testing".</p>
<p><img src="https://www.sergiofreire.com/assets/blog/shifting-towards-omni-testing-part2/omni1.png" alt="Atom Symbol representing Omni Testing with a brain icon, bug icon, other testing tools" loading="lazy"></p>
<p>Well, at first sight, “Continuous <whatever>” makes sense whenever everything else is using the “Continuous” prefix. But you don’t hear about “Continuous Coding”, do you?</p>
<p>Thus, a better adjective should be used instead: I think <strong>Omni</strong> (Testing) may describe Testing in a more complete way. Do you agree?</p>
<h2 id="continuously-evolve-our-way-of-understanding-the-world-around-us">Continuously evolve our way of understanding the world around us</h2>
<p>We moved from testing in waterfall, to <a href="https://agiletester.ca/ever-evolving-never-set-stone-definition-agile-testing/">testing in Agile</a>, to <a href="https://testingindevops.org/home/">testing in a DevOps context</a>. Testing, similarly to other processes, evolved along the way; its essence hasn’t.</p>
<p>We still use testing for the same reasons, because we aim to understand what we test and use that information to make decisions.</p>
<p>Whenever we go to a shop and buy something, whenever we download an app and try it out, whenever we meet or go out with someone: we’re trying to somehow understand what’s in front of us. We’re information seekers because ultimately we want to take actions leveraged on data, i.e. informed decisions.</p>
<p>In software development, which may also include hardware development, by the way, testing is done as a means to ensure that the deliverables produced on our products/solutions iterations have a certain level of quality. And this includes evaluating not only expected behavior but also that “customer” needs are covered in the best way, thus we look at certain quality attributes.</p>
<p>Testing is centered around humans and it requires them. However, machines, tools, algorithms, and AI can leverage the unique power we have within us to provide better testing. That’s around 3.5 billion years of continuous evolution and accumulated learning about life on Earth.</p>
<p>I know what you may be thinking: maybe there is no Agile Life, no Continuous Life, and we also don’t call it Omni Life right? Well, true; similarly to what happened with life, testing evolved, it’s widespread, it lives from the outskirts of software development. But, life is restricted in many ways: to certain conditions, to our planet (as far as we know).</p>
<p>Testing, on the other hand, is a broader concept; no limits attached... and being <em>Omni</em> is part of its real essence.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/shifting-towards-omni-testing-part2/cover.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Shifting Towards Omni Testing: Part 1]]></title>
            <link>https://www.sergiofreire.com/post/shifting-towards-omni-testing-part1</link>
            <guid>https://www.sergiofreire.com/post/shifting-towards-omni-testing-part1</guid>
            <pubDate>Mon, 15 Feb 2021 10:30:00 GMT</pubDate>
            <description><![CDATA[<p><strong>This is a revised article of the one <a href="https://testingindevops.org/shifting-towards-omni-testing/">originaly written</a> for "Testing in DevOps" community, from Lisa Crispin et al.</strong></p>
<p>Testing, as we foreseek, is something not limited to time, not limited to space, not limited to a specific team member, not limited to a given type, technique or approach; it is a process we use to understand something, to obtain more information about it so that we can take actions on it.</p>
<p>Testing is not just about continuously "running” (checks); testing is always there, it is part of the whole (e.g. product development), it encompasses different kinds of activities and involves everyone.</p>
<p>That’s why <strong>Omni Testing</strong> can, perhaps, be a better way of describing Testing as we aim it to be.</p>
<blockquote>
<p>Testing is universal. It’s one thing every single human has performed in their life.</p>
</blockquote>
<p>For many of us working in software or hardware product development, testing is even more "natural". However, we sometimes misinterpret it or see it just from a narrow perspective.</p>
<p>Lately, we have been hearing about Continuous Testing, Agile Testing, and DevTestOps/DevTestSecOps. Before that, Shift-Left and Shift-Right Testing and "Test Automation” were also around.</p>
<p>This tells us that Testing is:</p>
<ul>
<li>broad</li>
<li>important</li>
<li>always present</li>
<li>intermingled with what we do</li>
<li>used interchangeably, in different contexts</li>
</ul>
<p>Because of all of this, I think that Testing is a process unbound, in time, location, people and context; yet, it always has a purpose – actionable information. No matter if you’re using it to verify if you retained knowledge after attending a course, to evaluate if you like some food or a perfume, or to understand how a product works and if it meets some customer needs. Even before software was invented, testing was already part of our lives and helping us shape our decisions.</p>
<h2 id="omni-testing">Omni Testing</h2>
<p>Why Omni? Is it a "new thing” or a "new kind” of testing?</p>
<p>What types of tests and approaches does it embrace? Who is involved in it?</p>
<p>"Omni” comes from the Latin word omnis and means "all,” "the whole of, "all things,” "everything.”</p>
<p>More and more, teams everywhere are aiming to have a holistic approach to testing. We’re aiming to have ongoing conversations, ongoing experiments and investigations, ongoing checks, ongoing information, and ongoing feedback. The whole team is involved in testing and has quality in mind, from the start. For that, the team starts by assessing all risks, performs all sorts of tests, using all sorts of techniques and approaches.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/shifting-towards-omni-testing-part1/mindmap.png" alt="Mind map of omni testing showing people, machines, multifaceted, active, embracing modern approaches" loading="lazy"></p>
<p>(Software) Testing is multi-faceted; it can be used in so many ways, although in Omni Testing we try to perform it in a way where:</p>
<ul>
<li>it produces relevant information ASAP (i.e. accordingly with the agreed test strategy and the overall agreed meaning of quality)</li>
<li>it focuses on what matters first</li>
<li>it is efficient and quickly adapts</li>
</ul>
<p>Thus, it is not dependent/limited to:</p>
<ul>
<li>specific roles</li>
<li>specific assets</li>
<li>specific test levels, types, techniques and approaches</li>
<li>specific methodologies</li>
<li>specific phases</li>
<li>specific tools</li>
</ul>
<h2 id="defining-testing-again">Defining Testing, again</h2>
<p>Defining "testing” although may seem simple at first sight, it isn’t. Why? Because it is a broad concept and * sometimes used incorrectly or in a very restricted sense.</p>
<p>Recently, I had an opportunity of attending a workshop on exploratory testing with <a href="https://twitter.com/nkelln">Nancy Kelln</a> at <a href="https://www.linkedin.com/pulse/my-first-experience-testbash-germany-s%C3%A9rgio-freire/">TestBash 2019</a>.</p>
<p>To the question "why do we perform testing?" the group came to these results.</p>
<ul>
<li>Spend time/resources</li>
<li>Understanding the system</li>
<li>Uncover gaps</li>
<li>Provide valuable info to team members</li>
<li>Give everyone feedback</li>
<li>Finding risks</li>
<li>Make the end user happy</li>
<li>Translate information to others</li>
<li>Ready to ship?</li>
</ul>
<p><img src="https://www.sergiofreire.com/assets/blog/shifting-towards-omni-testing-part1/why_testing_testbash.jpg" alt="why do we perform testing?" loading="lazy"></p>
<p>Probably, others would come to different conclusions. For some, testing is just about checking and "finding bugs". However, it’s much more than that.</p>
<p>I do agree with <a href="https://testing.pejgan.se/2019/03/12/testing-vs-checking-separate-entities-or-part-of-a-whole-7/">Lena Pejgan on DevOps</a> that pictures such as the following one won’t help, as they depict "test” as a phase (which besides not being true is also not very agile).</p>
<p><img src="https://www.sergiofreire.com/assets/blog/shifting-towards-omni-testing-part1/devops.png" alt="DevOps infinite loop of plan, code, build, test, release, deploy, operate, monitor, plan..." loading="lazy"></p>
<p>There’s an <a href="https://www.youtube.com/watch?v=twXSKLI_vGs">in-depth explanation about Software Testing</a> from <a href="https://twitter.com/eviltester">Alan Richardson</a>, where he goes over the definition from Cem Kaner (The Ongoing Revolution in Software Testing, 2007):</p>
<blockquote>
<p>Testing is an empirical, technical investigation conducted to provide quality-related information about a software product to a stakeholder.
-- Cem Kaner</p>
</blockquote>
<p>What the previous definition subtly highlights is this investigative and exploratory nature of testing. This is why we cannot neglect this crucial aspect. <a href="https://twitter.com/maaretp">Maaret Pyhäjärvi</a> emphasizes Exploratory Testing, where we as curious, smart, and context-aware humans are driven and challenged by it, even when we do test automation, to provide valuable information that can drive decisions and fix problems ASAP.</p>
<blockquote>
<p>Exploratory Testing is an approach to testing that centers the person doing testing by emphasizing intertwined test design and execution with continuous learning where next test is influenced by lessons on previous tests.
-- Maaret Pyhäjärvi</p>
</blockquote>
<p>In testing, as Alan Richardson correctly points, we build models and we compare them with reality. <a href="https://twitter.com/charrett">Anne-Marie Charrett</a> has a short, clear <a href="https://www.youtube.com/watch?v=Ag1h81ka29g">video on Models &#x26; Software Testing</a> which may be useful to you in this context.</p>
<p>When focusing on software testing, you’ll probably find some terms: "Traditional", Waterfall, Agile, Continuous, Exploratory and Modern Testing (you may want to look at <a href="https://twitter.com/alanpage">Alan Page</a> and <a href="https://twitter.com/BrentMJensen">Brent Jensen</a> <a href="https://www.angryweasel.com/ABTesting/modern-testing-principles/">"Modern Testing Principles"</a>; there’s a <a href="https://www.ministryoftesting.com/dojo/courses/introduction-to-modern-testing-alan-page">course</a> on MoT).</p>
<p>Let’s look at <a href="https://www.satisfice.com/blog/archives/1509">this</a> testing definition:</p>
<blockquote>
<p>Testing is the process of evaluating a product by learning about it through exploration and experimentation, which includes: questioning, study, modeling, observation and inference, output checking, etc. -- James Bach &#x26; Michael Bolton</p>
</blockquote>
<p>I would rephrase this slightly, adding my perspective.</p>
<p>Testing is a <strong>targeted</strong> process of <strong>challenging something (e.g. a product) and our understanding about it</strong>, using a set of activities leveraged by specific human skills.</p>
<p>Where,</p>
<ul>
<li><em>"targeted process"</em> – a set of activities, having a common goal (i.e. quality attributes in mind), that will produce value-related information</li>
<li><em>"challenging"</em> –  includes trial/experimentation/exploration, checking, ...</li>
<li><em>"activities"</em> – build models, create test ideas, execute actions in the SUT, investigation, ask questions, provide feedback, ...</li>
<li><em>"human skills"</em> – creativity, curiosity, perseverance, analytical thinking, communication, courage, continuous learning mindset, ...</li>
</ul>
<p>Testing is <a href="https://www.satisfice.com/blog/archives/1509">exploratory</a> by nature; we try things and test ideas, we explore to seek understanding, to obtain information as Dan Ashby states in his <a href="https://danashby.co.uk/2017/12/13/a-new-model-for-test-strategies/">adapted model</a> of James Bach’s <a href="https://www.satisfice.com/download/heuristic-test-strategy-model">Heuristic Test Strategy Model</a>. We also use other approaches, such as script-based tests (i.e. checks) which sometimes are automated to verify "compliance” with predefined rules. We use all the outputs we collect as data that we process. This data becomes information, which is then used to improve our learning of the system/product and provide feedback to "stakeholders" (e.g. business, product team).</p>
<p>Whenever testing something we have in mind certain <a href="https://en.wikipedia.org/wiki/List_of_system_quality_attributes">quality attributes</a> (e.g. correctness, usability, scalability) – "requirements" we demand or expect from it.</p>
<p>To perform it, we test at different levels and use a bunch of different techniques that help us shape what we call "tests".</p>
<p>We test everything, software, hardware, specifications, established ideas, documents, and specifications.</p>
<p>Thus, testing is a complete, omni process that uses best-of-breed human skills to answer tailored questions to clarify our understanding about something and that can help shape it before, during and after it is built.</p>
<p>That being said, how does it relate with Shift-Left/Shift-Right and Continuous Testing? We’ll dive further into this topic on the next blog post of this series related to Omni Testing.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/shifting-towards-omni-testing-part1/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Model-based and sequential testing: a brief comparison]]></title>
            <link>https://www.sergiofreire.com/post/model-based-testing-mot-challenge</link>
            <guid>https://www.sergiofreire.com/post/model-based-testing-mot-challenge</guid>
            <pubDate>Fri, 06 Nov 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[<p>In this post I will do a brief comparison between "traditional" (i.e. sequential) automated tests/checks with MBT (Model-Based Testing), using some concrete examples including code, to reflect about it.</p>
<p>I made my code and my findings available as open-source in this <a href="https://github.com/bitcoder/automation_week_mot">GitHub repo</a>. Feel free to checkout the code, fork it, run it, play with it, whatever :)</p>
<p>If you want, you can skip the details and jump to the <a href="#conclusions">conclusions</a>.</p>
<h2 id="background">Background</h2>
<p>As part of <a href="https://club.ministryoftesting.com/t/what-is-automation-week/43663">Automation Week</a>, Ministry of Testing <a href="https://club.ministryoftesting.com/t/automation-week-test-applications/43664">challenged</a> testers to address some problems related to test automation; these would include implementing automated checks/tests for the UI of a dummy booking platform.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/target_sut.jpg" alt="Restful Booker Platform of &#x22;Shady Meadows B&#x26;B&#x22;" loading="lazy"></p>
<p>The idea would be to share the problems, the approach, the learnings with the community.
Selected reports would be able to present during <a href="https://www.ministryoftesting.com/events/test-dot-bash-online-2020">Test.Bash('Online') 2020</a>, and I was one the lucky ones to do so.</p>
<p>For me this was an opportunity to learn Python, even though I've made some limited open-source contributions in the past. It was also a way to explore Model-Based Testing (MBT) further.
My goal was also to find use cases and limitations, pros and cons (?) of MBT.</p>
<h2 id="overview-of-the-web-ui-challenges">Overview of the Web UI challenges</h2>
<p>Next follows a description of the UI challenges for the sample website application <a href="https://automationintesting.online/">Restful Brooker Platform</a> kindly provided by <a href="https://twitter.com/2bittester">Mark Winteringham</a> / <a href="https://twitter.com/FriendlyTester">Richard Bradshaw</a>.
As you'll see, challenges are pretty straightforward. The last one requires a bit more care to implement though.</p>
<h3 id="challenge-1">Challenge 1:</h3>
<p><em>Create an automated test that completes the contact us form on the homepage, submits it, and asserts that the form was completed successfully.</em></p>
<h3 id="challenge-2">Challenge 2:</h3>
<p><em>Create an automated test that reads a message on the admin side of the site.</em></p>
<p><em>You’ll need to trigger a message in the first place, login as admin, open that specific message and validate its contents.</em></p>
<h3 id="challenge-3">Challenge 3:</h3>
<p><em>Create an automated test where a user successfully books a room from the homepage.</em>
<em>You’ll have to click ‘Book this Room’, drag over dates you wish to book, complete the required information and submit the booking.</em></p>
<h2 id="approach-for-implementing-automated-tests">Approach for implementing automated tests</h2>
<p>I decided to follow two different approaches, so I could compare one another:</p>
<ul>
<li>the first one using pytest, with "standard" automated checks made of sequential actions/expectations</li>
<li>another using Model-Based Testing (MBT), using <a href="https://altom.gitlab.io/altwalker/altwalker/">AltWalker</a> which in turn uses <a href="https://graphwalker.github.io/">GraphWalker</a></li>
</ul>
<p>Both make use of the Page Objects Model (POM) facilitated by the pypom library. As pages can have different sections/regions, we can abstract those precisely as classes inherited from the Region class. This will make code cleaner and more readable.</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">FrontPage</span><span class="token punctuation">(</span>Page<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Interact with frontpage."""</span>

    _admin_panel_locator <span class="token operator">=</span> <span class="token punctuation">(</span>By<span class="token punctuation">.</span>LINK_TEXT<span class="token punctuation">,</span> <span class="token string">"Admin panel"</span><span class="token punctuation">)</span>

    <span class="token keyword">class</span> <span class="token class-name">ContactForm</span><span class="token punctuation">(</span>Region<span class="token punctuation">)</span><span class="token punctuation">:</span>

        _contact_form_name_locator <span class="token operator">=</span> <span class="token punctuation">(</span>By<span class="token punctuation">.</span>ID<span class="token punctuation">,</span> <span class="token string">"name"</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
</code></pre></div>
<p>In both implementations, you'll see references to a faking data library. I've combined controlled randomization of data to provide greater coverage; this is especially valuable in the MBT implementation as the model can be exercised automatically "indefinitely" (to a certain point).</p>
<h3 id="standard-tests-using-pytest">Standard tests using pytest</h3>
<p>This implementation is what I would call the "traditional" (i.e. common way) of implementing automated tests, where you implement a set of sequential actions and one, or more, asserts/expectaction checks.</p>
<p>The following code is an example one such tests (you can find more in in the file <a href="https://github.com/bitcoder/automation_week_mot/blob/main/standard_pom_tests.py">standard_pom_tests.py</a>).</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">test_contact_form_successful</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
    page <span class="token operator">=</span> FrontPage<span class="token punctuation">(</span>self<span class="token punctuation">.</span>driver<span class="token punctuation">,</span> BASE_URL<span class="token punctuation">)</span>
    page<span class="token punctuation">.</span><span class="token builtin">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>wait_for_region_to_load<span class="token punctuation">(</span><span class="token punctuation">)</span>
    page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>fill_contact_data<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"sergio"</span><span class="token punctuation">,</span> email<span class="token operator">=</span><span class="token string">"sergio.freire@example.com"</span><span class="token punctuation">,</span> phone<span class="token operator">=</span><span class="token string">"+1234567890"</span><span class="token punctuation">,</span>
                                        subject<span class="token operator">=</span><span class="token string">"doubt"</span><span class="token punctuation">,</span> description<span class="token operator">=</span><span class="token string">"Can I book rooms up to 2 months ahead of time?"</span><span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>assertEqual<span class="token punctuation">(</span>page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>contact_feedback_message<span class="token punctuation">,</span>
                      <span class="token string-interpolation"><span class="token string">f"Thanks for getting in touch sergio!\nWe'll get back to you about\ndoubt\nas soon as possible."</span></span><span class="token punctuation">)</span>
</code></pre></div>
<p>In this case data was initially hard-coded. However, by using <a href="https://faker.readthedocs.io/en/master/">faker</a> library we can create a <a href="https://github.com/bitcoder/automation_week_mot/blob/main/tests/my_contact_provider.py">custom test data provider</a> for the contact and our test method can be rewritten as:</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">test_contact_form_successful</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
    page <span class="token operator">=</span> FrontPage<span class="token punctuation">(</span>self<span class="token punctuation">.</span>driver<span class="token punctuation">,</span> BASE_URL<span class="token punctuation">)</span>
    page<span class="token punctuation">.</span><span class="token builtin">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>wait_for_region_to_load<span class="token punctuation">(</span><span class="token punctuation">)</span>
    name <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_name<span class="token punctuation">(</span><span class="token punctuation">)</span>
    email <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_email<span class="token punctuation">(</span><span class="token punctuation">)</span>
    phone <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_phone<span class="token punctuation">(</span><span class="token punctuation">)</span>
    subject <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_subject<span class="token punctuation">(</span><span class="token punctuation">)</span>
    description <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_description<span class="token punctuation">(</span><span class="token punctuation">)</span>
    page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>fill_contact_data<span class="token punctuation">(</span>
        name<span class="token operator">=</span>name<span class="token punctuation">,</span> email<span class="token operator">=</span>email<span class="token punctuation">,</span> phone<span class="token operator">=</span>phone<span class="token punctuation">,</span> subject<span class="token operator">=</span>subject<span class="token punctuation">,</span> description<span class="token operator">=</span>description<span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>assertEqual<span class="token punctuation">(</span>page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>contact_feedback_message<span class="token punctuation">,</span>
                      <span class="token string-interpolation"><span class="token string">f"Thanks for getting in touch </span><span class="token interpolation"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token string">!\nWe'll get back to you about\n</span><span class="token interpolation"><span class="token punctuation">{</span>subject<span class="token punctuation">}</span></span><span class="token string">\nas soon as possible."</span></span><span class="token punctuation">)</span>
</code></pre></div>
<p>Using a controlled custom test data generator can be quite helpful as we'll see ahead. It's also a way of handling test data, as long as it is not fully random (as you may want to reproduce things afterwards).</p>
<h3 id="model-based-tests-using-altwalker-and-graphwalker">Model-based tests using AltWalker and GraphWalker</h3>
<p>With MBT, usually we have the model (either made using a visual model editor or from the IDE) and the underlying code.
In our case, models are stored in JSON format under the <a href="https://github.com/bitcoder/automation_week_mot/blob/main/models">models</a> directory.
The test code associated with vertices and edges is implemented in the file <a href="https://github.com/bitcoder/automation_week_mot/blob/main/tests/test.py">test.py</a>.</p>
<p>Using <a href="https://altom.gitlab.io/altwalker/model-editor/">Model Editor</a> (or <a href="https://graphwalker.github.io/">GraphWalker Studio</a>), we can model our application using a directed graph. In simple words, each vertex represents a state and each edge is a transition/action made in the application. Tests are made on the vertices/states.</p>
<p>Modeling is a challenge in itself and we can model the application and how we interact with it in different ways. Models are not exhaustive; they're a focused perspective on a certain behavior that we want to understand. MBT provides greater coverage and also a great way to visualize and discuss the application behavior/usage.</p>
<p><strong>Addressing challenge 1 with MBT</strong></p>
<p>For the first challenge (i.e. contact form submission), we start from an initial state, from where we just have one possible action/edge: load the frontpage.
Then we can consider another state, where the frontpage is loaded and the contact form is available.
Two additional states are possible: one for a successful contact and another for an unsuccessful contact submission. We can go to these states by either submitting valid or invalid contact data.</p>
<p><u>One curious thing comes out from the model: after a successful contact, we can only make a new contact if we load/refresh the frontpage again.</u> Was this an expected behavior? Well, we would have to discuss with the team.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/mbt_contact_form.jpg" alt="" loading="lazy"></p>
<p>With GraphWalker Studio, we can run the model offline and see the paths (sequence of vertices and edges) performed.</p>
<p>The code for each vertex and edge is quite simple as seen ahead.</p>
<p>Example of <strong>e_submit_valid_contact_data</strong> code, showcasing usage of faker library:</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">e_submit_valid_contact_data</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">:</span>
    page <span class="token operator">=</span> FrontPage<span class="token punctuation">(</span>self<span class="token punctuation">.</span>driver<span class="token punctuation">,</span> BASE_URL<span class="token punctuation">)</span>
    page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>wait_for_region_to_load<span class="token punctuation">(</span><span class="token punctuation">)</span>

    name <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_name<span class="token punctuation">(</span><span class="token punctuation">)</span>
    email <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_email<span class="token punctuation">(</span><span class="token punctuation">)</span>
    phone <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_phone<span class="token punctuation">(</span><span class="token punctuation">)</span>
    subject <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_subject<span class="token punctuation">(</span><span class="token punctuation">)</span>
    description <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_description<span class="token punctuation">(</span><span class="token punctuation">)</span>

    data<span class="token punctuation">[</span><span class="token string">'global.last_contact_name'</span><span class="token punctuation">]</span> <span class="token operator">=</span> name
    data<span class="token punctuation">[</span><span class="token string">'global.last_contact_email'</span><span class="token punctuation">]</span> <span class="token operator">=</span> email
    data<span class="token punctuation">[</span><span class="token string">'global.last_contact_phone'</span><span class="token punctuation">]</span> <span class="token operator">=</span> phone
    data<span class="token punctuation">[</span><span class="token string">'global.last_contact_subject'</span><span class="token punctuation">]</span> <span class="token operator">=</span> subject
    data<span class="token punctuation">[</span><span class="token string">'global.last_contact_description'</span><span class="token punctuation">]</span> <span class="token operator">=</span> description

    page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>fill_contact_data<span class="token punctuation">(</span>
        name<span class="token operator">=</span>name<span class="token punctuation">,</span> email<span class="token operator">=</span>email<span class="token punctuation">,</span> phone<span class="token operator">=</span>phone<span class="token punctuation">,</span> subject<span class="token operator">=</span>subject<span class="token punctuation">,</span> description<span class="token operator">=</span>description<span class="token punctuation">)</span>
</code></pre></div>
<p>Example of <strong>v_contact_successful</strong> code:</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">v_contact_successful</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">:</span>
    page <span class="token operator">=</span> FrontPage<span class="token punctuation">(</span>self<span class="token punctuation">.</span>driver<span class="token punctuation">,</span> BASE_URL<span class="token punctuation">)</span>
    name <span class="token operator">=</span> data<span class="token punctuation">[</span><span class="token string">'last_contact_name'</span><span class="token punctuation">]</span>
    subject <span class="token operator">=</span> data<span class="token punctuation">[</span><span class="token string">'last_contact_subject'</span><span class="token punctuation">]</span>
    self<span class="token punctuation">.</span>assertEqual<span class="token punctuation">(</span>page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>contact_feedback_message<span class="token punctuation">,</span>
                      <span class="token string-interpolation"><span class="token string">f"Thanks for getting in touch </span><span class="token interpolation"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token string">!\nWe'll get back to you about\n</span><span class="token interpolation"><span class="token punctuation">{</span>subject<span class="token punctuation">}</span></span><span class="token string">\nas soon as possible."</span></span><span class="token punctuation">)</span>
</code></pre></div>
<p>If we were using just this model, then the contact details could be temporarily stored as regular object variables (each model, in code side, is an object). However, that would limit us in case we want to have shared states between models, as we'll see ahead.</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">e_submit_valid_contact_data</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">:</span>
    page <span class="token operator">=</span> FrontPage<span class="token punctuation">(</span>self<span class="token punctuation">.</span>driver<span class="token punctuation">,</span> BASE_URL<span class="token punctuation">)</span>
    page<span class="token punctuation">.</span>contact_form<span class="token punctuation">.</span>wait_for_region_to_load<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token comment"># variables could be saved in python side, in this object, but we'll need to share them between models which use a different class &#x26; object</span>
    self<span class="token punctuation">.</span>name <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_name<span class="token punctuation">(</span><span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>subject <span class="token operator">=</span> fake<span class="token punctuation">.</span>valid_subject<span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
</code></pre></div>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/mbt_contact_form_offline.gif" alt="" loading="lazy"></p>
<p>One can make this model a bit more detailed and complex, by making explicit edges/transitions for the process of submitting one field as invalid. This makes the graph harder to read though and it will only be relevant if we want to distinguish those cases.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/mbt_contact_form_detailed.jpg" alt="" loading="lazy"></p>
<p>But we have to ask ourselves? Is it worth implementing this distinction? What are we trying to achieve/model exactly?</p>
<p><strong>Addressing challenge 2 with MBT</strong></p>
<p>In order to validate if the contact/message appears correctly in the admin page (2nd challenge), we start from a vertex/state related to a successful contact. Note that this vertex has a <strong>shared state</strong> with the first model shared earlier, which allows AltWalker/GraphWalker to jump from one model to the other one.</p>
<p>We can then go to the admin panel, authenticate if needed, go to the inbox/messages section, open and check the details of the last contact message.
We can see several edges corresponding to actions that can be done, allowing us to transverse the graph and thus go to different application states.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/mbt_message_backoffice.jpg" alt="" loading="lazy"></p>
<p>Some edges have "actions" defined in the model, to set an internal variable that can be useful later on, in case we want to conditionate the paths somehow.</p>
<p>Example of action defined in <strong>e_admin_correct_login</strong>:</p>
<div class="remark-highlight"><pre class="language-js"><code class="language-js">logged_in<span class="token operator">=</span><span class="token boolean">true</span><span class="token punctuation">;</span>
</code></pre></div>
<p>Some edges have "guards", so they're only performed if those guard conditions are true. In the following example, the edge is only executed if not yet logged in (which uses a variable set before in an action).</p>
<p>Example of guard defined in <strong>e_click_admin_panel</strong> (from <strong>v_contact_successful</strong> to <strong>v_admin_login</strong>):</p>
<div class="remark-highlight"><pre class="language-js"><code class="language-js">logged_in<span class="token operator">!=</span><span class="token boolean">true</span>
</code></pre></div>
<p>In this exercise and on the previous one, we take advantage of using model variables (e.g. last_contact_name, last_contact_subject) to temporarily store information about the last contact so we can make the asserts on the vertex later on. The contact data details are filled in code side and are populated back to model variables used for this purpose. Passing data between edges/vertices code and the model can be done using an optional argument (e.g. "data") on the respective methods. Variables will be serialized/deserialized (use it wisely).</p>
<div class="remark-highlight"><pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">e_submit_valid_contact_data</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        data<span class="token punctuation">[</span><span class="token string">'global.last_contact_name'</span><span class="token punctuation">]</span> <span class="token operator">=</span> name
        data<span class="token punctuation">[</span><span class="token string">'global.last_contact_email'</span><span class="token punctuation">]</span> <span class="token operator">=</span> email
        data<span class="token punctuation">[</span><span class="token string">'global.last_contact_phone'</span><span class="token punctuation">]</span> <span class="token operator">=</span> phone
        data<span class="token punctuation">[</span><span class="token string">'global.last_contact_subject'</span><span class="token punctuation">]</span> <span class="token operator">=</span> subject
        data<span class="token punctuation">[</span><span class="token string">'global.last_contact_description'</span><span class="token punctuation">]</span> <span class="token operator">=</span> description
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
</code></pre></div>
<p>On model side, variables can be local to each model or they can be global. Global variables are useful in case we need to access some information from another model.
Having a ton of global variables can be hard to manage though.</p>
<p><strong>Addressing challenge 3 with MBT</strong></p>
<p>Challenge 3 (i.e. new booking) can also be addressed using a simple model, having a variable named <em>total_nights</em>, defined at model level, for controlling the intended number of nights to book.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/mbt_new_booking1.jpg" alt="" loading="lazy"></p>
<p>The previous model depicts a sequential set of actions and corresponding states, so it mimics a typical sequential automated test as seen in the pytest implementation.
Even though feasible, and as there's only one path in the graph, this model, as it is, doesn't provide exceptional value except that it turns visible our own model of the system.</p>
<p><em>Note: another possible model could deal with the fact that the contact and date selection don't need to happen in sequence, and also provide the ability to jump back to the initial page. Well, many variations can be done depending on what we want to verify and the risks we have in mind.</em></p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/mbt_new_booking2.jpg" alt="" loading="lazy"></p>
<h2 id="conclusions">Conclusions</h2>
<p>Traditional, sequential tests can be easy to implement and read (its code). However, they can become harder and harder to read (depending on code quality aspects). More, these tests are good for checking happy paths and doing negative testing. However, their focus coming from the hard-coded path that they exercise, is also their limitation.</p>
<p>With MBT I'm not focused on simple happy paths or negative tests. Instead, I'm trying to look at my feature from different angles, like "all" possible ways of interacting with it.
Therefore, coverage with MBT increases because many more paths are exercised. By walking though those paths, we can expose hidden risks that otherwise we would miss.</p>
<p>Modeling is a process that in itself is also an uncovering mechanism for unknowns. During the exercise, I could see that certain actions could only be performed if I was at a given "state"... and that could make sense or not. For example, to make a new contact request, I had to refresh the front page. There, while modeling, as a process that we iterate over, we are also performing exploratory testing. Having models can be beneficial for discussion, even if they do not have automation code linked to them.</p>
<p>Surprisingly, the code related to MBT is simple and clean. At the start I had some doubts about it.
I would say that one important challenge is related to how to pass information between models, in case we want to have multiple models interacting with one another, through shared states.
By combining controlled test data generation with MBT, we can keep our model being exercised until an error arises or a certain amount of time has elapsed. This is a great way to spend time with automation code: we're not running the same tests, we're running many different tests/paths along with different data.</p>
<p>Please find ahead a sum up of my thoughts on traditional tests vs model-based tests. It's not an exhaustive comparison but it's a starting point for additional learning iterations.</p>
<p><strong>Traditional, sequential automated tests/checks:</strong></p>
<ul>
<li>are simple and focused</li>
<li>are good for happy path and negative testing</li>
<li>have restricted coverage, even if we use data-driven testing</li>
<li>can become hard to visualize (we need to infer the "visual model" by looking at the code)</li>
</ul>
<p><strong>Model-Based Testing:</strong></p>
<ul>
<li>Focused on a model, not on a concrete example</li>
<li>Doesn’t replace "traditional" tests/checks</li>
<li>Greater coverage, beyond happy/negative tests</li>
<li>Visualization fosters discussion/reflecting</li>
<li>Ability to reuse and combine models</li>
<li>It’s a way of performing exploratory testing!</li>
</ul>
<p><strong>MBT: Challenges and Tips:</strong></p>
<ul>
<li>Many ways to model: keep it simple!</li>
<li>Write models, even if you don’t automate them</li>
<li>Increase coverage further with test data randomization</li>
<li>Temporary data management (model vs code)</li>
<li>Harder to debug</li>
</ul>
<h2 id="learn-more">Learn more</h2>
<ul>
<li><a href="https://github.com/bitcoder/automation_week_mot">GitHub repo with code for the two approaches</a></li>
<li><a href="https://altom.gitlab.io/altwalker/altwalker/overview.html">AltWalker</a></li>
<li><a href="https://graphwalker.github.io/">GraphWalker</a></li>
<li><a href="https://altom.gitlab.io/altwalker/model-editor/#/visual-editor">Visual model editor for AltWalker and GraphWalker</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=Altom.altwalker-model-visualizer">AltWalker Model Visualizer for VSCode</a></li>
</ul>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/model-based-testing-mot-challenge/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Model-based testing (MBT): quick overview]]></title>
            <link>https://www.sergiofreire.com/post/model-based-testing-overview</link>
            <guid>https://www.sergiofreire.com/post/model-based-testing-overview</guid>
            <pubDate>Wed, 30 Sep 2020 15:30:00 GMT</pubDate>
            <description><![CDATA[<p>For some time I have heard about model-based testing technique and I wanted to know a bit more about it. I will share my learnings in several posts to make digestable.
In this article I will give a high-level overview of model-based testing and some initial doubts and preliminary findings along with resources.</p>
<p>A couple of weeks ago, I was lucky to be able to attend an <a href="https://bbst.courses/state-model-based-testing-workshop/">online workshop from Black-Box Software Testing</a> on State Model Based Testing. The workshop instructors (<a href="https://twitter.com/altomalex">Alexandru Rotaru</a> and Dorin Oltean) conceived it as being hands-on, which was great because makes learning more efficient.</p>
<p>In the first day we spent some time modeling a simple web application; in the second one, we implemented test automation code to support our model.
Actually, we implemented the coded as a group using Ensemble Programming practice. We were lucky to have <a href="https://twitter.com/maaretp">Maaret Pyhäjärvi</a> also attending, which was great as introduced this concept to us and we all were able to see its benefits in practice.</p>
<h2 id="what-is-state-model-based-testing">What is State Model-Based Testing?</h2>
<p>If we look at the following model, we can easily understand in few seconds that we're talking about requesting an Uber and there are certain actions we can do meanwhile, that may affect or not the state, whatever it means.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-overview/model_uber.jpg" alt="MBT model example" loading="lazy"></p>
<p>This is the power of visual models.</p>
<p>MBT (Model-Based Testing) is a testing technique that uses models as basis. A model is a "formal, simplified representation of a relationship, a process or a system" (quoting Alex Rotaru).</p>
<p>In other words, we can see that MBT:</p>
<ul>
<li>it's a formalization</li>
<li>it's a way to describe something relevant to the system and its usage/behavior</li>
<li>it's not exhaustive (i.e. it's not totally accurate)</li>
<li>it's focused on testing, although we can see similirities to DDD (Domain Driven Development)</li>
</ul>
<p>In SMBT models are built as directed graphs, composed of states (i.e. vertices) and transitions (i.e. edges).
This way, it's easy to visualize and have a discussion around it.</p>
<p>After we build out our model, we use a tool to generate a skeleton for our test automation code. The actual test code needs to be implemented (some commercial tools provide this ability to an extent).</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-overview/model1.jpg" alt="MBT model example" loading="lazy"></p>
<p>Finally, we can run tests exercising our model. The idea is to go over the vertices (states) in the model using the possible edges (transitions/actions) and use is that in a way that maximizes coverage; I'm oversimplifying it though.
To "walk" in the model (i.e. to go from one vertex to another one using a specific edge), we need to have a "path".
The MBT tool can generate an incredible high number of paths, using a "generator" algorithm, and stopping it when a certain "stop condition" is achieved.</p>
<p>And that's it: we have now a way to perform interactions in our SUT accordingly to the model we've defined earlier.</p>
<p><em>Note however that MBT can be used even without automation as a way to talk about the system behavior and support manual testing activities (scripted or even exploratory).</em></p>
<h2 id="why-model-based-testing">Why Model-Based Testing?</h2>
<p>But why do we need MBT after all?</p>
<p>Well, if we think on any system (web or non-web based), it's really hard to test all possible usage scenarios.</p>
<p>Implementing traditional test automation, implementing a test script one after another for validating a specific use case takes considerable time. And more than that: their coverage will always be limited, even if we use data-driven testing.</p>
<p>Therefore, MBT is an excellent technique to increase coverage through more diversified test scenarios.
It can also be used to quickly understand legacy systems without having to deep dive into the code specifics.</p>
<h2 id="doubts">Doubts</h2>
<p>I had a bunch of doubts at start, as usual.</p>
<ul>
<li>When should I use SMBT?</li>
<li>What are the scenarios where SMBT is not the best fit?</li>
<li>What can we map as being a vertex (i.e. a "state")?</li>
<li>What are the edges (i.e. the "transitions")?</li>
<li>Where should we implement the assertions?</li>
<li>Semantically, what is the test scope here? An automated test script exercises a specific use case, and what happens with MBT?</li>
<li>What's the relation to requirements, features, user stories?</li>
</ul>
<p>Some of these doubts are starting to fade away as I go through real experiments but there's no right/wrong answers for most of them.</p>
<p>Meanwhile, several doubts arise. One that is not yet totally clear to me is related to state management:</p>
<ul>
<li>Where to deal with it? In the model susing the vertices themselves, as variables set in the model, or as internal variables managed in the underlying test automatiob code? Where's the border?</li>
</ul>
<h2 id="modeling-and-its-challenges">Modeling and its challenges</h2>
<p>What we do when we test is to create a model of our systems in our brain and then we refine it through experimentation and validation.</p>
<p>This model is not unique, though.</p>
<p>Imagine yourself, for a moment, talking about some dream house that you've seen and that you would love to buy. Now think about someone else talking about that same house.</p>
<p>Do you think that you would be looking at it from the same perspective? Or would each individual see themselves having their own experiences, mostly different, in that house?
More, one could think on the house as either being "under construction", "ready for living", or "inhabited" while the other one could think on it as being "with people resting", "having a party", "having lunch", etc.</p>
<p>Therefore, how we model a system can be done in many ways.
But where to start?</p>
<p>If we have a website (e.g e-commerce site), we can model it from the UI perspective, where each state corresponds to a page. Transitions are made through interactions users make (e.g. go to homepage, find owners).</p>
<p>However, this is not the only possible modeling approach; we can think on the state of a transaction and model that for example. So, yes, we can use MBT to model beyond just UI interactions and use it to model algorithms, for example.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/model-based-testing-overview/model2.jpg" alt="MBT model example" loading="lazy"></p>
<p>Modeling requires experience because it's a simplification on reality based on a perspective that matters. All of that is subjective, have you noticed?</p>
<h2 id="mbt-and-unknowns">MBT and unknowns</h2>
<p>Testing is about learning, learning more about what we're building, why we're building, how its being built, and how users tasting that special "meal" that is our product. Does it taste well? Is it missing something? Was it delivered at the right time, to the right users?</p>
<p>Well, with testing we try to uncover all these unknowns that surround our products and that live inside them.
Model-based testing can be used as a great technique to expose some unknowns and confirm certain behaviors of our products.</p>
<p>Models are easy to understand and visualize, and also can be easy to automate and check.
By writing an executable model, we can (in)validate assumptions and then refine it. We can even fork and create additional models to tackle different aspects.</p>
<p>Modeling has also one interesting consequence: it promotes discussion. By promoting discussion, unknowns can be tackled on people's heads. Therefore, pairing or doing it as a group (ensemble programming) can provide additional benefits.</p>
<h2 id="tooling">Tooling</h2>
<p>There are several tools out there, including open-source ones. Probably the most popular is <a href="https://graphwalker.github.io/">GraphWalker</a>, which is tailored for Java skilled users (as it generates Java code and then can run it).</p>
<p>Python and C# users may prefer to use <a href="https://altom.gitlab.io/altwalker/altwalker/">AltWalker</a> instead. AltWalker uses GraphWalker on the backstage.</p>
<p>Commercial tools are also available; I've seen some but not yet tried them myself. I would say that there are at least two great differences:</p>
<ul>
<li>visual editors are better</li>
<li>test automation code generation supports many more languages and frameworks</li>
</ul>
<h3 id="tips">Tips</h3>
<p>If you are new on this topic, I already have some tips that may be useful.</p>
<ul>
<li>Don't try to make complex models as they will be hard to read and maintain</li>
<li>Have few vertices and edges; focus on the essential</li>
<li>Spliting the model in several and use shared states, if it makes sense</li>
<li>Even if we're talking about the same feature, having different models can provide different angles to it and to expose different bits of it</li>
</ul>
<h2 id="learn-more">Learn more</h2>
<p>I would recommend the following resources to get your hands dirty :)
Any of the two examples is simple to understand and to get running.</p>
<ul>
<li><a href="https://github.com/GraphWalker/graphwalker-project/wiki/PetClinic">GraphWalker e-commerce site (Java example)</a></li>
<li><a href="https://altom.gitlab.io/altwalker/altwalker/examples/python/e-commerce-demo.html">altwalker e-commerce site (Python example)</a></li>
</ul>
<p>If you want to know more about some specific open-source tools, that also talk a bit about the rational of model-based testing, please check:</p>
<ul>
<li><a href="https://graphwalker.github.io/">GraphWalker</a> => the <a href="https://github.com/GraphWalker/graphwalker-project/wiki">wiki pages on Github</a> provide several examples and explain concepts</li>
<li><a href="https://altom.gitlab.io/altwalker/altwalker/overview.html">AltWalker</a></li>
<li><a href="https://altom.gitlab.io/altwalker/model-editor/#/visual-editor">Visual model editor for AltWalker and GraphWalker</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=Altom.altwalker-model-visualizer">AltWalker Model Visualizer for VSCode</a></li>
</ul>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/model-based-testing-overview/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[The Quality Ice Cream Truck]]></title>
            <link>https://www.sergiofreire.com/post/the-quality-ice-cream-truck</link>
            <guid>https://www.sergiofreire.com/post/the-quality-ice-cream-truck</guid>
            <pubDate>Thu, 16 Jul 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>In software and in testing we use models a lot. Models allow us to create a perspective of something real and easily visualize its internal and its relations.
The "Quality Ice Cream Truck" is a model, and as such it represents a fallible, non-perfect, perspective on value and its relations to quality and testing. I'm always trying to refine and clarify concepts for myself, to help me understand, see the gaps and also talk about them.</p>
<p>In the Quality Ice Cream Truck all is around <strong>value</strong> as we'll see ahead.</p>
<h2 id="what-is-value">What is value?</h2>
<p>Many of us think that value is something highly well-defined, static, and probably around visible features.
However, value is subjective, it changes with time... and it's just more than features.
Quality defines value, by defining what is relevant for the stakeholders. Here I'm talking about quality criteria (e.g. correctness, usability, scalability, etc).
By agreeing what composes value, then the team can use that to drive other collaborative activities.</p>
<h2 id="distilling-the-quality-ice-cream-truck-model">Distilling the "Quality Ice Cream Truck" model</h2>
<p>First, I hope this model visually resembles an ice cream truck, at least for me it does :)
It's an evolution of a previous model on the relationship between Quality, Value, Testing and Risks by Rich Rogers.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/the-quality-ice-cream-truck/model.jpg" alt="Quality Ice Cream Truck" loading="lazy"></p>
<p>Value is at the center and it's the actual driver of our ice cream truck; it's what we and our users want to "taste" and enjoy.
We know that people like different flavours, sizes, consistencies, etc, of ice creams. It makes sense since value is derived from our own definition of quality.</p>
<p>Knowing what is relevant will shape our testing, which in turn will provide us information on our quality.
Testing informs us on (negative) risks, opportunities and also gaps. We can consider a defect as a risk in this model, since we can decide how to handle it (i.e. its <em>treatment</em>, from a Risk Management perspective).
Even though we may consider that testing is about risk assessment, and treatment as a natural consequence, I would like to highlight opportunities and gaps for a moment because they're forgotten so many times. Having in mind value, testing looks at all spots where we can increase and optimize it. That's why I look at testing not just as bug preventing/finding but as all other ways that can ultimately give us tips to increase and accelerate the delivery of value.</p>
<p>Testing <strong>does not</strong> affect or assure value directly. Testing produces information, hopefully shared and discussed as a team, that we can act on. By acting on the information, we may affect value and try to maximize it.
Value is directly affected by how we code/build the product and how we operate and maintain it.</p>
<p>Since value is served on production, we need to understand what is happening there to fine-tune it ASAP.
Operations and support informs us about how value is being delivered, served and tasted.
We do use monitoring tools to provide some insights even though they're limited. We can go a step further and use observability to analyze raw data, trace requests, see patterns, understand what is happening, i.e. to explore and ask questions to the system that we don't know in advance.
This allows us to highlight risks and confirm the value of our product by looking at its usage. It can also give us tips on possible opportunities and gaps that we can try to address.</p>
<h2 id="final-considerations">Final considerations</h2>
<p>There are for sure more relations and more entities that we could represent in this model but I tried to simplify it to a point.
As we saw, information comes from multiple sources. From those, testing outputs are essential to provide insights about quality. To improve the product itself and its value, we need shared understanding and collaboration, so that necessary changes, no matter if it's in the product itself or in the surrounding processes, can be implemented... no matter by whom.</p>
<p>I challenge you to make your own version of quality, testing, value and activities around it. Putting it on paper/screen can foster some healthy discussions and bring to the table all the different aspects that contribute to delivering value to customers... which is always a team effort leveraged by the continuous understanding of what value represents.</p>
<h2 id="useful-references">Useful references</h2>
<ul>
<li><a href="https://developers.google.com/web/tools/lighthouse">A model of the relationship between Quality, Value, Testing and Risks</a></li>
<li><a href="https://www.ministryoftesting.com/dojo/lessons/the-quality-and-testing-information-model">The Quality and Testing Information Model: Getting Important Quality-Related Information to the Right People</a></li>
</ul>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/the-quality-ice-cream-truck/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Improving performance, security, accessibility, SEO and more]]></title>
            <link>https://www.sergiofreire.com/post/improving-performance-security-accessibility-and-more</link>
            <guid>https://www.sergiofreire.com/post/improving-performance-security-accessibility-and-more</guid>
            <pubDate>Sun, 05 Jul 2020 17:00:00 GMT</pubDate>
            <description><![CDATA[<p>I want to share with you how I dramatically improved some key aspects of my personal website, including performance, accessibility and more. Performance increased by more than 300%! And I did it in the Agile way, using one <em>secret</em> of Agile many times forgotten: I hope it becomes clear throughout this article.</p>
<p>I'm curious by nature, maybe due to my deep and intrinsic "test nature" :) but I also have some bits of a "crazy scientist": I like to experiment by myself, watch, taste and learn.</p>
<p>Last year, I <a href="/post/my-website-at-last-in-agile-way">launched</a> this blog. I told to my self: "It's time to do it... so let's do it in an Agile way!". I looked at different SaaS (e.g. Wordpress, Wix) and choosed Wix.
As I started adding some content and working with the site backoffice, I realized it was getting really slow and full of bloatware.
I confirmed this qualitative feeling with some metrics(obtained using <a href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a>) that made me even more upset.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-performance-security-accessibility-and-more/old_site_audit.jpg" alt="old site audit" loading="lazy"></p>
<p>There were many problems showing up in the report besides performance.
How could I solve this?
Well, sometimes you have to take a step back; it will hurt a bit but it will provide you so many benefits on the run.</p>
<h2 id="time-go-back-to-the-basics">Time go back to the basics</h2>
<p>I asked myself:</p>
<p><em>What do I really need?</em></p>
<p>And the answer was:</p>
<ul>
<li>share some blog posts, once in a while, in a very easy way</li>
<li>provide great experience for visitors, in terms of readability, accessibility, performance</li>
</ul>
<p>Some nices-to-have would be:</p>
<ul>
<li>ability to move the blog easily to another host</li>
<li>easily track and revert changes</li>
<li>ability to easily make experiments, safely</li>
</ul>
<p>After applying the changes herein detailed, some things I got besides the basics:</p>
<ul>
<li>on-demand live environments, per commit and per PR (pull request)</li>
<li>easy backups (it's Git so...)</li>
<li>ability to switch hosting provider (e.g. Vercel, Heroku)</li>
<li>automated checks, per branch/PR using <a href="https://www.checklyhq.com/">Checkly</a></li>
<li>ability to easily bring in another contributor</li>
</ul>
<p>Things I got rid of:</p>
<ul>
<li>low-speed</li>
<li>accessibility issues</li>
<li>bloatware</li>
<li>lock-in on Wix</li>
<li>Wix banner at top</li>
<li>Wix cost</li>
</ul>
<p>Let's see how I dit it.</p>
<h2 id="how-it-started">How it started</h2>
<p>I was reading about <a href="https://jamstack.org/">JAMstack</a>; (more info on <a href="https://jamstack.wtf/">JAMstack WTF</a>) and I ended up in this <a href="https://rauchg.com/2020/develop-preview-test">blog about E2E testing</a>. Suddenly I discovered <a href="https://nextjs.org/">Next.js</a>, <a href="https://vercel.com/">Vercel</a> and <a href="https://www.checklyhq.com/">Checkly</a>.</p>
<p>The idea behind JAMstack is to provide a development environment for web sites, using <strong>J</strong>avascript, <strong>A</strong>PIs and <strong>M</strong>arkup, where the latter is served as static, generated files without requiring explicitly a web server.
Even though this has some constraints that limit its broader usage, it provides many benefits in terms of performance and security that can be applied in many cases.
In case you've worked with blog kind of sites, you may have crossed with tools, such as (Jekyll)[https://jekyllrb.com/), that generate static sites.
What makes JAMstack fun is that you can add dynamic behaviour to your static sites, using Javascript.</p>
<h2 id="my-experiment">My experiment</h2>
<p>From the <a href="https://nextjs.org/docs/basic-features/pages">Next.js documentation</a>, I could try out some live examples of this framework and also have access to their respective source.
It was really quite straightforward to create a blog and deploy it to Vercel in no time, either by cloning one of the repos or from the command-line.</p>
<div class="remark-highlight"><pre class="language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/vercel/next.js/tree/canary/examples/blog-starter.git
<span class="token comment"># or ...</span>
<span class="token function">yarn</span> create next-app <span class="token parameter variable">--example</span> blog-starter blog-starter-app
</code></pre></div>
<p>Then I could do some changes, evaluate the results with Lighthouse and also try the tools by myself.
Let me take the opportunity to introduce them at a glance.</p>
<h3 id="lighthouse">Lighthouse</h3>
<p><a href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a> is a tool that looks at certain quality aspects of your site, including performance, accessibility, SEO, PWA (Progressive Web Applications) support. It can be run from the command-line, CI or directly from within Chrome dev tools.</p>
<p>Lighthouse provides a score for each high-level item and then allows you to drill-down and look at the individidual checks where you may find additional information and recommendations, so that you can improve your site.
It's quite simple and effective, for testers and developers; it makes easier to track some quality aspects from the start.</p>
<p>Some care though:</p>
<ul>
<li>score will depend on how and where you execute it from; if you're using Chrome, then it is highly recommended using an anonymized window so it doesn't get affected by any extensions that you may have</li>
<li>if use a tool such as <a href="https://lighthouse-keeper.com/">Lighthouse Keeper</a> or other one that integrates with your toolchain, than results may be slightly distinct</li>
</ul>
<h3 id="vercel">Vercel</h3>
<p><a href="https://vercel.com/">Vercel</a> is a cloud-based orchestrator platform, tailored for "building" and deploying/hosting static sites; Vercel is also the team behind Next.js.
As a developer, you can write the code, push it and Vercel will do the rest:</p>
<ul>
<li>watch a given branch and checkout the code from the Git source control provider</li>
<li>build the site static files</li>
<li>create a temporary environment and deploy static files there</li>
<li>assign a custom DNS entry to the environment</li>
</ul>
<p>Vercel has several integrations, including with <a href="https://vercel.com/github">GitHub</a>; thus, one can also track the deployment on GitHub pull-requests.</p>
<h3 id="checkly">Checkly</h3>
<p><a href="https://www.checklyhq.com/">Checkly</a> is a simple, yet powerful tool for DevOps teams; it can "accelerate" the delivery of software by providing feedback about your site based on browser (or API) checks, implemented using Puppeteer scripts.
The checks can run from multiple locations and besides tracking the assertion results, one can also take screenshots and track historical assertion results and response times. On top of this, we can be alerted upon failures.</p>
<p>Checkly also integrates with GitHub, which is awesome; thus, we can also automatically run check and track their results whenever doing pull-requests. This provides valuable information before merging.</p>
<h2 id="implemented-workflow">Implemented workflow</h2>
<p>My workflow will be mostly based on <a href="https://nvie.com/posts/a-successful-git-branching-model/">git-flow</a> but without the "develop" branch; I'll use branches to implement fixes/new features on my site. I will then create PRs and merge then if appropriate.</p>
<p>My website will be hosted on GitHub; I will also enable integrations with Vercel and Checkly, to ease deployments and have almost real-time feedback on my pull-requests.</p>
<p>In Checkly, I can quickly track my site status and performance.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-performance-security-accessibility-and-more/checkly_main.jpg" alt="checkly" loading="lazy"></p>
<p>I've created a check that goes over the main pages. This can be split into distinct checks but in this case I don't see the need for it as I don't aim to grow these.</p>
<div class="remark-highlight"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> assert <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"chai"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">assert</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> puppeteer <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"puppeteer"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> browser <span class="token operator">=</span> <span class="token keyword control-flow">await</span> puppeteer<span class="token punctuation">.</span><span class="token method function property-access">launch</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> page <span class="token operator">=</span> <span class="token keyword control-flow">await</span> browser<span class="token punctuation">.</span><span class="token method function property-access">newPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> baseUrl <span class="token operator">=</span>  process<span class="token punctuation">.</span><span class="token property-access">env</span><span class="token punctuation">.</span><span class="token constant">ENVIRONMENT_URL</span> <span class="token operator">||</span> <span class="token string">"https://myblog.bitcoder.vercel.app"</span>
<span class="token keyword">var</span> url <span class="token operator">=</span> baseUrl
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">goto</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span>  title <span class="token operator">=</span> <span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">title</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">screenshot</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">"homepage.png"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
assert<span class="token punctuation">.</span><span class="token method function property-access">equal</span><span class="token punctuation">(</span>title<span class="token punctuation">,</span> <span class="token string">'Sergio Freire\'s personal blog on software testing, agile'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>



<span class="token keyword">var</span> url <span class="token operator">=</span> baseUrl <span class="token operator">+</span> <span class="token string">"/about"</span>
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">goto</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
title <span class="token operator">=</span> <span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">title</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
assert<span class="token punctuation">.</span><span class="token method function property-access">equal</span><span class="token punctuation">(</span>title<span class="token punctuation">,</span> <span class="token string">'About'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">screenshot</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">"about.png"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">var</span> url <span class="token operator">=</span> baseUrl <span class="token operator">+</span> <span class="token string">"/contact"</span>
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">goto</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
title <span class="token operator">=</span> <span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">title</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">screenshot</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">"contact.png"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
assert<span class="token punctuation">.</span><span class="token method function property-access">equal</span><span class="token punctuation">(</span>title<span class="token punctuation">,</span> <span class="token string">'Contact'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> url <span class="token operator">=</span> baseUrl <span class="token operator">+</span> <span class="token string">"/articles"</span>
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">goto</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
title <span class="token operator">=</span> <span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">title</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">await</span> page<span class="token punctuation">.</span><span class="token method function property-access">screenshot</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">path</span><span class="token operator">:</span> <span class="token string">"articles.png"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
assert<span class="token punctuation">.</span><span class="token method function property-access">equal</span><span class="token punctuation">(</span>title<span class="token punctuation">,</span> <span class="token string">'Articles'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword control-flow">await</span> browser<span class="token punctuation">.</span><span class="token method function property-access">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre></div>
<p>We can run the check manually right away.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-performance-security-accessibility-and-more/checkly_test_results.jpg" alt="checkly results" loading="lazy"></p>
<p>Additionally, we may integrate it with GitHub so our check(s) is/are run automatically in PRs.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-performance-security-accessibility-and-more/checkly_link_repo.jpg" alt="checkly" loading="lazy"></p>
<p>I've also implemented another check that will transverse my blog posts. In this iteration, the blog post URLs are hardcoded. But I aim to automate this properly.</p>
<p>![checkly](/assets/blog/improving-performance-security-accessibility-and-more/checkly_posts_script.jpg</p>
<h3 id="example">Example</h3>
<p>Imagine that I wanted to add a new feature on my website, or to fix something, or to make an improvement in general..
I would start by creating a branch.</p>
<div class="remark-highlight"><pre class="language-bash"><code class="language-bash"><span class="token function">git</span> checkout <span class="token parameter variable">-b</span> fake_improvement
</code></pre></div>
<p>Then I would make all the code changes I wish and commit them.</p>
<div class="remark-highlight"><pre class="language-bash"><code class="language-bash"><span class="token function">git</span> <span class="token function">add</span> <span class="token punctuation">..</span>.
<span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">"my changes"</span>
<span class="token function">git</span> push <span class="token parameter variable">-u</span> origin fake_improvement
</code></pre></div>
<p>In GitHub I could see the new branch and decide to create a PR for it.
Whenever looking at the pull-request, we can can see a section from Vercel giving us a link to the "build logs# and also to a preview environment having our branch code deployed.
We can then go to that environment and see how our web looks like and perform some exploratory testing on it.
But we can also have immediate feedback about the automated checks implemented using Checkly. In the following screenshot we can see that our two "automated tests" failed; this happened on purpose as I deleted one post and changed the title of the website.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-performance-security-accessibility-and-more/github_pr_feedback.jpg" alt="github PR feedback" loading="lazy"></p>
<p>We can also see the details (and re-run) of the test.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-performance-security-accessibility-and-more/github_checkly_detail.jpg" alt="github checkly detail" loading="lazy"></p>
<p>By having near real-time environments and test results, it eases me performing changes in the future.
After fixing the problems, I can finally merge my branch (as easy as clicking on merge on GitHub) and if deployed successfully it gets assigned to my domain automatically (by Vercel).</p>
<h2 id="further-improvements-for-the-future">Further improvements for the future</h2>
<p>Checkly</p>
<ul>
<li>explore its potential further</li>
<li>automate obtaining all blog posts</li>
<li>check all site references for dead links</li>
</ul>
<p>Lighthouse</p>
<ul>
<li>evaluate how to integrate Lighthouse in GitHub, to have information on the PRs</li>
<li>evaluate how to have checks based on some criteria for the Lighthouse provided metrics</li>
</ul>
<p>Blog</p>
<ul>
<li>review styles (e.g. cover, code blocks)</li>
<li>review image sizes/formats</li>
</ul>
<h2 id="key-learnings">Key learnings</h2>
<ul>
<li>don't be stuck in the past</li>
<li>don't be afraid to try, especially if you can make a quick experiment and obtain some quick results</li>
<li>Lighthouse is a great audit tool for web sites/apps (mobile/desktop), essential for testers and developers</li>
<li>there are some tools (e.g. Vercel, Checkly, etc) that can easily integrate with your (source-code) versioning control provider and your delivery pipeline</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Your project is never complete; it won't ever have all the things you wished for, nor the ones you didn't even think about.</p>
<p>Sometimes, you just have to start. You cannot wait for a ton of evaluations that will provide you all the things you need to decide; no evaluation is ever complete, IMO.</p>
<p>If you're working in a project from the start, then you can keep your eyes open and use tools such as Lighthouse to provide some quality insights. If your project is already alive, then a tool such as Lighthouse is also valuable for your team; as a tester you can look at some improvement opportunities and discuss them within the team and developers can also proactively fix.</p>
<p>After I've implemented my blog site, I realized that it was not meeting its ultimate purpose: being accessible, readable, high-performing, clean. If you leave a bad impression, it will be hard for visitors to return.</p>
<p>Choosing the mix GitHub + Verce + Next.js + Checkly + Lighthouse provided me the necessary bits to overcome my and my reader needs. This doesn't mean it's the best mix or that I won't change it in the future. But now I know that I've simplified what I had, improving key quality metrics, removing lock-ins and making it less expensive.</p>
<p>At the start of this article I've mentioned one secret of Agile, remember?
Well, the secret is that, IMO, being <strong>Agile is not about adding more features, in an incremental way. Instead, it's about incrementally adding value.</strong> In other words, this means that sometimes it's about reducing what you have, making it cleaner, focused. And this may imply putting most (if not all) of what you have into the garbage and start it from scratch. Actually, you never start from scratch because your previous experience can help you get where you want, faster.</p>
<h2 id="some-other-curious-stuff">Some other curious stuff</h2>
<ul>
<li>Vercel also integrates with Lighthouse. I actually discovered it whenever I was having a coffee, using my smartphone. I added it and obtained the results right away in few seconds. How impressive is that?</li>
<li>Migrating my posts from Wix to Next.js took me a bit as I had to rewrite them in markdown. But now I'm getting used to it :)</li>
</ul>
<h2 id="useful-references">Useful references</h2>
<ul>
<li><a href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a></li>
<li><a href="https://web.dev">web.dev</a></li>
<li><a href="https://nextjs.org/">Next.js</a></li>
<li><a href="https://www.checklyhq.com/">Checkly</a></li>
<li><a href="https://vercel.com/">Vercel</a></li>
</ul>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/improving-performance-security-accessibility-and-more/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Unknown Series on Testing E01: The Needs]]></title>
            <link>https://www.sergiofreire.com/post/unknown-series-on-testing-e01-the-needs</link>
            <guid>https://www.sergiofreire.com/post/unknown-series-on-testing-e01-the-needs</guid>
            <pubDate>Mon, 11 May 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>In our first episode about unknowns, we will go to the starting point, i.e. to our big bang in software development: the needs.
We will probably need to distil this in additional articles; but that’s exactly what happens whenever you deal with the unknown: it’s something that you need to keep exploring.</p>
<h2 id="feedback-loops-can-be-lengthy-and-error-prone">Feedback loops can be lengthy and error-prone</h2>
<p>Having worked around product development all my professional life, I dealt with requirement elucidation and analysis for some years. Writing or reviewing an extensive and detailed specification for a system or even for a specific feature of a product is hard.</p>
<p>This scenario is normal in waterfall projects where the release date is normally several months ahead and it's seen as a deadline, to you and to your team.
After having the specification sorted out, to a point, we start creating some other documentation for the architecture of what we're going to build.
Then, implementation starts and verification is performed in bulk before the release is moved to an acceptance-testing phase with the customer. But that moment can be too late to receive feedback from customers or stakeholders. Maybe you just built the wrong product: it happens.</p>
<p>Agile aims to shorten the feedback loops, namely the one between the customer and the development team. By delivering small pieces of value continuously, customers can provide feedback and features can be better tailored to the actual needs.
Clearly this process is more dynamic than waterfall; however, do you think it's immune to  problems around what is being built versus actual needs? Well, no.</p>
<p>One underlying "problem" is that we as humans use language to communicate, express feelings or needs. As the communication chain increases, it also increases the noise level within it.
I remember playing a funny game as a kid: a bunch of us stood together and started saying a message (e.g. a gossip) to the kid next to each other. The message would be said in very low tone and passed from kid to kid until reaching the one that originally said it. When the message was received at the starting point and was expressed loudly, we would laugh for a while. In fact, most times it had nothing to do with the original one.</p>
<h2 id="uncovering-unknowns-in-communication">Uncovering unknowns in communication</h2>
<p>How we express ourselves is noisy and so is its interpretation. And this happens everytime we communicate, and not only at a given moment in time.
One way to deal with it is by having frequent customer interactions along with the team right before the project starts and also during its lifetime.
Why is it important?</p>
<p>Well, for several reasons:</p>
<ul>
<li>It helps create a shared understanding and uncover many unknowns</li>
<li>It helps clarify concepts, terms and needs</li>
<li>It helps identify and assess risks</li>
<li>It helps prioritization and understanding what’s important and what is not</li>
</ul>
<p>At a project's start, you can do a “<a href="https://www.agilealliance.org/glossary/three-amigos">three amigos</a>” session... well, you can involve more than 3 people representing business, development, and testing units. I think having someone from UX and support is important, independently of whether you have them as separate roles or not.
Probably you may have experienced design thinking before; “<a href="https://medium.com/productmanagement101/design-sprints-at-google-85ff62fed5f8">design sprints</a>” are built on top of this concept.
Design sprints provide a well-defined approach to have prototypes that can be tested by users, even before they’re implemented.</p>
<p>I tried a smaller variant of the design sprint concept but the idea is the same:
Have customer present with the team
Understand concepts, needs, where’s the real value and what is the ultimate purpose
Diverge to get some ideas of possible solutions
Converge to evaluate if some solutions are functional viable</p>
<p>During the project implementation and rollout, we should have frequent interactions with our stakeholders or the customer. Customers can be present in daily Scrums; I've worked in Agile-based projects with frequent (almost daily) customer interactions. I also know how it hurts whenever you can’t have that feedback on-time or even until the “last minute” of your deadline.
No matter if we’re starting the project or you’re mi , questioning is really the.</p>
<h2 id="needs-vs-solutions">Needs vs Solution(s)</h2>
<p>Most of the time, people will come to you with the solution. And they can even give you that as a “requirement”.</p>
<p><em>Hey, we need a button here that does this.</em>
<em>Hey, we need to generate a report with these columns.</em></p>
<p>Humans are creative and you know what? Everyone likes to “innovate” and have that “I had this amazing idea” feeling.</p>
<blockquote>
<p>The problem is that ideas are like cocoa: we need to process them to have chocolate (i.e. highlight the needs).</p>
</blockquote>
<p>The <a href="https://en.wikipedia.org/wiki/Five_whys">5 whys technique</a> provides a way of getting to the root cause, or in our case - the need.
Questioning is in fact a skill testers have, and we all have, that can help us uncover what’s behind some closed door.
Remember that we aim to optimize our overall effort building and testing the right product.</p>
<h2 id="pains-are-also-needs">Pains are also Needs</h2>
<blockquote>
<p>It’s not just about uncovering and delivering “new features”; it’s also about discovering the pains and the gaps.</p>
</blockquote>
<p>Pains are blockers to deliver value: they take time, effort, and costs. They distract us from thinking about ideas that could improve our product and increase the value provided by it.
So, we need to discover these pains, which involves on-going conversations with the stakeholders/customers.
Pains are gaps or background noise in our “requirements”, if we’re working on an existing product.</p>
<p>Some ways of uncovering these are:</p>
<ul>
<li>ask what the different personas do in their daily product usage</li>
<li>if you can, observe (or monitor) people using the product.</li>
</ul>
<h2 id="how-is-all-this-related-to-testing">How is all this related to testing?</h2>
<p>It’s really important that we uncover unknowns from the start or else we begin to generate assumptions or misconceptions that will guide our implementation and our testing to paths.
Only if we understand the actual needs, what is the ultimate goal, we can implement and test what we’re building properly.
Many times we get excited implementing unnecessary features that will just increase our tech and test debt… and also the “needs debt”, if we can call it that.
Having different perspectives from the start, helps clarify concepts, needs, concerns and thus uncover unknowns.
Testing is about looking for information and understanding which starts at the big bang: right at the moment the universe of unknowns around your product begins.
Please feel free to share your thoughts on how you uncover needs and help build a better-tailored product.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/unknown-series-on-testing-e01-the-needs/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Introducing the Unknown Series on Testing]]></title>
            <link>https://www.sergiofreire.com/post/introducing-the-unknown-series-on-testing</link>
            <guid>https://www.sergiofreire.com/post/introducing-the-unknown-series-on-testing</guid>
            <pubDate>Tue, 28 Apr 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>Some time ago, I decided to tackle a tough problem: the unknown.
I do love tough problems because they make me think, question, and ultimately have a better understanding even if that understanding is acknowledging that I don’t have an answer… yet.</p>
<p>I remembered attending a testing-related lecture that presented a way of visualizing the known and unknown in a matrix which left me very curious.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/introducing-the-unknown-series-on-testing/UST_E00_unknowns_and_knowns_matrix.png" alt="Unknowns and knowns matrix" loading="lazy"></p>
<p>After a quick search, I reached this <a href="https://www.youtube.com/watch?v=REWeBzGuzCc">video from Donald Rumsfeld</a> that introduces and explains it briefly. There is in fact a great correlation between this perspective and testing.  Testing, as I see it, is this hunt for information and understanding leveraged by unique human skills. It is quite diverse and broad (please check <a href="https://www.sergiofreire.com/articles">my articles on Omni Testing</a>), as information has so many different variables.</p>
<p>Testing is highly related to uncovering unknowns; even if we have a high degree of confidence in the system and we know what to expect, we perform testing because we will only know that for sure after testing!</p>
<p>I decided to start a series on the unknown to exercise and refine some of my thoughts and share them with the community.
In this series, I will uncover bits of the unknown, its relation to testing, and how we can use it to obtain information that may help shape what and how we build our products.</p>
<p>The unknown may be vast, scary, useless, and even intangible. At least that’s what we may think at first glance. But, the unknown can also be surprising, exciting, valuable, and discoverable. These are the two faces of the unknown. We can look at the unknown from the “dark side” or from the “bright side”.</p>
<p>The same happens with testing products: we can look at it from any of those perspectives. I prefer the positive one but I do understand that sometimes it’s hard to avoid that “dark side”, especially if we jump into a project with a high <a href="https://www.sergiofreire.com/post/improving-testing-through-testing-debt-quadrants">testing debt</a> and low testability.</p>
<p>As I walk this journey I would like to understand a bit more about the unknown, including discovering important questions like:</p>
<ul>
<li>What is the extent of knowns and unknowns in products?</li>
<li>What types of unknowns there are?</li>
<li>Where are the unknowns?</li>
<li>Tactics to uncover/expose unknowns?</li>
<li>Is there any approach to testing and/or techniques that can better address unknowns? Are we ignoring something that could provide us more valuable information?</li>
<li>How are we addressing the unknowns in scientific areas?</li>
<li>What have we learned about the unknowns so far?</li>
<li>Are our knowns solid? Or are they an illusion, mostly temporary?</li>
<li>How to search within the unknown in a proficient way?</li>
<li>How to increase the probability of lucky findings?</li>
<li>How do humans interfere in the process of acquiring knowledge?</li>
<li>How do we achieve conclusions?</li>
</ul>
<p>By being aware of our limitations and also of our capabilities, we can achieve better testing. By “better” I mean it in a broader sense (e.g. effort, the relevance of information).</p>
<p>Stay tuned as I uncover bits of the unknown, together with you.
If you have questions, you’re not alone.
Remember, the “truth is out there” :)</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/introducing-the-unknown-series-on-testing/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Simple, yet useful tips for working remotely effectively]]></title>
            <link>https://www.sergiofreire.com/post/simple-yet-useful-tips-for-working-remotely-effectively</link>
            <guid>https://www.sergiofreire.com/post/simple-yet-useful-tips-for-working-remotely-effectively</guid>
            <pubDate>Fri, 13 Mar 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>A long, long time ago, in a distant galaxy, I started working in a company where I had an office just to myself and two colleagues. I remember that I had this music a bit loud; there was a colleague in the same space that was already "tired" of the same music :)
With the company growing, we move to an open space scenario which was more challenging, namely in terms of sound isolation and distraction.
I realized how to work in a shared space bit by bit, with my own mistakes; we all learned.</p>
<p>A few years ago I started working partially remote in my current company.
I have been a product manager and PO for a couple of years and then I became a solution architect. As a PM/PO I had to engage with the team frequently and that has its own challenges, namely, it requires discussing and explaining things often. Being with the team eases interactions and provides a better understanding of team dynamics and challenges within the team.
Even though we were at the office, we had collaboration in our DNA; this means we are open to help each other complete their tasks and goals. We also used and use tools to ease collaboration at many different layers (Slack/Teams, Jira, Confluence, Bitbucket).
Making the transition from office to my home (or to any other place), was mostly easy but I did it gradually.
My company is remote-friendly and that helps a lot; if you're already used to collaborate and use collaboration tools, then it's even more straightforward.
Nowadays, as a solution architect, I have many tasks that I need to perform by myself but many of them involve the team, as we work together to achieve goals and each one brings something to the table.
Working remotely may seem a huge challenge to you; it seemed to me. I dealt with it and nowadays, it's</p>
<h2 id="how-is-a-typical-day-working-remotely">How is a typical day working remotely?</h2>
<p>This is my "typical" remote day, especially if I'm working from home.</p>
<ul>
<li>have breakfast</li>
<li>take a bath and dress up as you would do normally if you had to go to office</li>
<li>go walk for a bit, sometimes having a coffee outside (this can help mark the ?* transition from personal to work mode)</li>
<li>start my work around 9am (aprox.)</li>
<li>have a break to see my birds or wash dishes (washing my dishes normally is quite brain-estimulating, curiously)</li>
<li>lunch at 12h30pm</li>
<li>walk a  bit (occasionally, run for 30min)</li>
<li>back to work mode at 1h30pm</li>
<li>some very small breaks in the afternoon around the house/watching my birds
stop working around 6pm (aprox.)</li>
</ul>
<p>Remember: it's important to keep mentally and physically healthy.
Having said this, here are my first tips for working efficiently remotely; there are many more; these are the first that came to my mind.</p>
<h2 id="dress-up-for-work">Dress up for work</h2>
<p>Exactly. Take your bath and dress up as you would normally do if you had to go to the office. Prepare for your day of work.</p>
<h2 id="delimit-your-work-time">Delimit your work time</h2>
<p>Imagine yourself going to the office and coming back home. Try to have the same kind of habit, even though you're working remotely.
It helps a lot to establish your working hours (e.g. 9am to 6pm); don't be afraid to adapt them a bit and add some randomness to them. One day you can start at 9h00, the other day at 9h15. Nobody will die because of it; we know that in IT it's hard to have fixed schedules but we can also use them to our own advantage. If you need to start a bit later, no worries. If you feel you may start sooner, also no worries. However, having some sort of working window will help you.
Whenever I started working remotely, I almost forget to have lunch. Sometimes I would lunch at 2pm or later, which for me is totally not normal.
Trying to adhere to a common working schedule will help you professionally and personally.</p>
<h2 id="have-breaks">Have breaks</h2>
<p>Working remotely doesn't mean you have to be working 100% of the time, without stopping for a bit. Having breaks (or the so-called "bio breaks") helps you being more productive. Unfocus and focus.</p>
<p>How to do this in practice?</p>
<ul>
<li>have a coffee/tea</li>
<li>take care of your pets and give them some hugs</li>
<li>go outside, have some air for a bit, if possible</li>
<li>walk a bit</li>
</ul>
<p><img src="https://www.sergiofreire.com/assets/blog/simple-yet-useful-tips-for-working-remotely-effectively/bird.jpg" alt="bird" loading="lazy"></p>
<h2 id="delimit-your-workspace">Delimit your workspace</h2>
<p>Have your own dedicated workspace helps: a specific room inside your house or a co-working office. Avoid mixing your personal space with the working place, that way you can better focus.</p>
<h2 id="find-inspiration">Find inspiration</h2>
<p>In your "lonely moments", you'll need inspiration.
Once in a while, I choose a totally different place for working; if viable, I go outdoors for 2 or 3 hours. It helps a lot in my mood and also my inspiration.</p>
<h2 id="focus">Focus</h2>
<p>Avoid distractions or work in places where they persist. For some people, it may be hard to stay focused. The good thing is that we can train ourselves. Think of what works best for you to stay focused and practice it.
Remember: it's important to keep focused; however, it's also important to have some small breaks.</p>
<h2 id="collaboration">Collaboration</h2>
<p>Depending on your background and your team background, collaboration can be easier or harder. I think that what is really important to have in mind is that work will be mostly done asynchronously even though you may have real-time tools.
Using collaboration tools helps a lot because they allow you to work asynchronously and notify whoever if necessary.  Thus, you can work in a Google doc or in a Confluence page and ask someone to review it; that person will be notified and will help you whenever possible.
Another thing to ease collaboration and to be available to others is by understanding each one individual goals and also overall team goals. You can use Trello, Jira to give visibility to what tasks everyone needs to achieve.
We are using a kind of Scrum-based approach: we use sprints, tasks and have planning meetings where we discuss things as a team.</p>
<h2 id="live-chatting">"Live" chatting</h2>
<p>If you use a live chat tool in your company (e.g. Teams, Slack) then you should be aware that:
the fact that you or anyone are online in that chat tool doesn't mean that you/they are available all the time
Chat tools can also be used as asynchronous tools. You can leave a message to a person/group and it may be answered later on. If what you're asking is urgent let others know, otherwise don't assume it. People will answer when they can. Remember that they can be completing other tasks or having their own break.</p>
<p>Some recommendations:</p>
<ul>
<li>use your online "status" to tell other ones about your availability, in an implicit way</li>
<li>configure your tool to not send you notifications all the time (for example, if you're busy or if it's totally outside your working hours</li>
<li>And please... don't call anyone without asking if that person is available first of all :)</li>
</ul>
<h2 id="be-with-the-team">Be with the team</h2>
<p>Try to be with the team physically whenever possible. Even whenever you're remote, you can still "be with the team". You can be fun and still maintain relations with team members. It's not all about work; it's also about the individual.
Have the time to talk and also to listen. Avoid loneliness/isolation.</p>
<h2 id="meet-humans">Meet humans</h2>
<p>You're working remotely right? That doesn't mean you should be isolated from the world. In times as we're facing currently with COVID-19, we should restrict ourselves. In more standard times, it's important to be with people in whatever activities (work or non-work related).
Things you can do:
lunch or have coffee with friends
attend meetups
go to the gym, dance or other activities
If you can't or isn't recommended to meet humans on person, well you can always give them a call or attend some online events/calls.</p>
<h2 id="personal-things">Personal things</h2>
<p>The fact that you're at home gives you also the ability to deal with personal things; sometimes you need a plumber to go to your house or other things.
That's ok; that's the benefit of being at home. If you were at the office, you would be stressed about dealing with it. The fact that you're remote also allows you to adapt to your schedule. You may need to leave or go get your kids... that's part of life.
Be sure to balance well professional and personal life; if you need to work a bit outside your normal schedule, let your partner be aware of it.</p>
<h2 id="in-sum">In sum...</h2>
<p>There are is so much more than the above.
If you see yourself suddenly working remotely, take a breath. You can make it.
I started doing it gradually; at first, it was a bit hard (I love human contact/interactions). I gradually adapted myself. I think I now work quite efficiently remotely (more than at the office sometimes). Don't forget to socialize even if it's done in a virtual way.
Set your workspace, set your working hours.
Take care of yourself and work as a team, knowing that others in the team are also having similar challenges as you do.
Every single of us is different; learn your differences, your challenges. There are ways to handle them.
You'll succeed, no doubts about it.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/simple-yet-useful-tips-for-working-remotely-effectively/me.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Improving Testing through Testing Debt Quadrants]]></title>
            <link>https://www.sergiofreire.com/post/improving-testing-through-testing-debt-quadrants</link>
            <guid>https://www.sergiofreire.com/post/improving-testing-through-testing-debt-quadrants</guid>
            <pubDate>Fri, 21 Feb 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>I was lucky to attend Euro Testing Conference 2020, in Amsterdam, last month; I learned so much.
One of the workshops that I attended was Rob Meaney's "Exploring Testability" which inspired me to try something within my team.
At the conference, we did a hands-on exercise related to testing debt. Testing what?! Debt!
This article will explain the testing debt concept, how to make it visible and how to address it.</p>
<h2 id="what-is-testing-debt">What is testing debt?</h2>
<p>We all heard about the technical debt concept before - the development decisions made to provide benefits in the short-term that will, however, inhibit benefits in the future; in other words, imagine doing a quick "hack" to provide a feature instead of properly choosing the right architecture to support it. Testing debt is similar.</p>
<p>Quoting Rob:</p>
<blockquote>
<p>Testing debt occurs when a team intentionally or unintentionally chooses an option that yields benefit in the short term but results in accrued testing cost in terms of time, effort or risk in the longer term.
-- Rob Meaney</p>
</blockquote>
<p>Peter Varhol, puts it slightly differently:</p>
<blockquote>
<p>The effort needed to find and fix issues that remain in the code when an application is released.
-- Peter Varhol</p>
</blockquote>
<p>In simple words: testing debt is all about the testing that you should have done but for some reason, you didn't.</p>
<h2 id="testing-debt-quadrants">Testing Debt Quadrants</h2>
<p>During Rob's workshop, we mapped our debt using Testing Debt Quadrants.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-testing-through-testing-debt-quadrants/testing_debt_eurotestconf.jpg" alt="testability workshop" loading="lazy"></p>
<p>The testing debt quadrants provide a way to visualize debt, including:</p>
<ul>
<li>untested things</li>
<li>testing that is slow or impractical and that can lead it to be forgotten/not performed</li>
<li>items/components that are complex to test</li>
<li>items you cannot rely on</li>
</ul>
<p>Therefore, we splitted the cardboard into 4 sections:</p>
<ul>
<li>impossible/impractical</li>
<li>slow</li>
<li>complex</li>
<li>untrustworthy</li>
</ul>
<p><strong>Note: instead of seeing all of the above as problems, they are in fact opportunities.</strong></p>
<p>The process starts by adding post-its, identifying a "piece of debt" (i.e. something that we are not testing thoroughly or the way we should).
As a team, we identify the ones that are most relevant (i.e. the ones that contribute the most to the testing debt). For each one of these, we discuss possible ways of addressing them and add them to new post-its. And now we have concrete actions that we can take to reduce our debt and improve the overall testing.</p>
<h2 id="from-theory-to-practice-how-we-did-it">From theory to practice: how we did it?</h2>
<p>After the conference, I wanted to try this exercise internally and see if it was valuable or not. I also wanted to make visible some pain-points on our testing process.
We did this with a small group, including myself, part of our testers and also the team lead. Even though my role was essentially a facilitator, as I've been the product owner of the product we are testing, I do have particular insights about it which I cannot neglect :)</p>
<p>We started by doing a retrospective on our current development &#x26; testing process. By drawing it, we could easily see who was doing what and when. It also allowed us to depict some improvement opportunities right away.
I see this kind of diagrams as starting points for discussion; some care should be taken though, or else this can lead to long discussions.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-testing-through-testing-debt-quadrants/pipeline_draft.jpg" alt="pipeline draft" loading="lazy"></p>
<p>We then moved to the Testing Debt Quadrants and starting writing and adding post-its to the 4 quadrants. Each one did this alone and in the end, we could see some overlap (which I'd say it is "normal" to happen).
Where we position each post-it in the cardboard is somehow subjective; however, as we reviewed the whole cardboard, we move some of them and agreed on it. Note that, for example, some items can be slow and complex at the same time.  Where the post-its end up doesn't need to be scientific; use "positioning" as a means for further discussion/insights.
We saw that we had some unfinished work that could tackle some of the identified problems. We just need to turn that into reality.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-testing-through-testing-debt-quadrants/testing_debt_quadrants_exercise.jpg" alt="testing debt quadrants exercise" loading="lazy"></p>
<h2 id="from-discovery-to-actions">From discovery to actions</h2>
<p>Having discovered and discussed part of the items that contribute to our testing debt, it's important to have actionable, prioritized items.
Thus, we decided to create a very simple Trello board to track their implementation and/or discuss anything around them.</p>
<p>It's interesting that as per our initial discussion during the "visual mapping" of our development/testing process, we could depict some opportunities to improve testing considerably. Thus, the board contained actions not only found during the Testing Debt Quadrants but also from the initial sketch we did about our overall process.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/improving-testing-through-testing-debt-quadrants/trello_board_testing_debt.jpg" alt="Trello board" loading="lazy"></p>
<h2 id="things-to-have-in-mind">Things to have in mind</h2>
<p>Whenever looking at its full extent, it's easy to get lost and start discussing a ton of different topics.</p>
<p>Some recommendations for us and for anyone trying it out:</p>
<ul>
<li>timebox it, so we don't extend it endlessly</li>
<li>focus (no interruptions)</li>
<li>involve other roles</li>
<li>think on improving gradually, a step at a time (this will avoid finding the "perfect solution")</li>
<li>revisit the cardboard regularly</li>
</ul>
<p>I would also recommend having the conversations in such a way where there is no pinpointing. This should be a team exercise, open but not individual-focused.</p>
<h2 id="and-now-what">And now what?</h2>
<p>I think mapping your testing issues, which are also opportunities to improve your overall testing process, using Testing Debt Quadrants is a simple and valuable exercise.
All the conversations you have are important to have a joint overview of how testing is being addressed and how it can be improved.
Just make sure that these conversations aren't pointless and, instead, lead to actions that you can track and revisit together somehow.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/improving-testing-through-testing-debt-quadrants/testing_debt_quadrants_exercise.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[European Testing Conference: my first and last experience and my takeaways]]></title>
            <link>https://www.sergiofreire.com/post/european-testing-conference-my-first-and-last-experience-and-my-takeaways</link>
            <guid>https://www.sergiofreire.com/post/european-testing-conference-my-first-and-last-experience-and-my-takeaways</guid>
            <pubDate>Mon, 10 Feb 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>Some time ago, I had heard that EuroTestConf  / ETC (i.e. European Testing Conference) was something different, where you could attend and learn so much, not just with the talks and workshops, but also with the attendees, no matter if they're speakers or not. Everyone has a perspective, everyone can bring something positive to the table.
You may learn and you also may provide feedback, insights and your own perspectives to others.
EuroTestConf had (yes, unfortunately, there won't be new editions) one main purpose: to show it was possible to have a great testing conference where speakers won't have to pay to speak (e.g. traveling, stay, ticket). Besides it, the idea was to promote diversity and inclusion (more about it <a href="https://europeantestingconference.blogspot.com/2019/08/whats-special-about-european-testing.html">here</a>). Another interesting aspect was the idea of bringing programmers and testers together, under the same "umbrella", so they could discuss together ideas to make their products more testable and better.</p>
<h2 id="warming-up">Warming up</h2>
<p>At the start, I felt a bit like a "stranger" in mid of the crowd: it was my first time there and I'm a bit always like that for a couple of minutes. Gladly, after few minutes I was starting conversations or joining ongoing ones. Every single person is different and not everyone may be in the mood to discuss something, but that's normal. Each one of us can be in a different mental state, at a given moment. We just have to acknowledge it and proceed.
In general, people are open to a conversation; like me and you, they're attending also to exchange ideas, talk about their problems and their goals.
Of course, if you see someone around that you've seen elsewhere, perhaps in a different conference, then you feel a bit more like comfy.
I was really glad to see some recent, yet recurring testing friends :), such as ]João Proença](https://twitter.com/jrosaproenca) and <a href="https://twitter.com/lisacrispin">Lisa Crispin</a>, that besides being kind also are natural helpers.
It was also great to meet <a href="https://twitter.com/janetgregoryca">Janet Gregory</a> once again and exchanging some ideas with her.
<a href="https://twitter.com/RobMeaney">Rob Meaney</a> became a new addition to my testing friends, especially after he met personally the one who "bugged him with questions" some days ago, in a webinar on operability :)
We also had a lovely dinner together with <a href="https://twitter.com/roesslerj">Jeremias Roessler</a> where we discussed a bit of all.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/testing_friends.jpg" alt="testing friends" loading="lazy"></p>
<h2 id="mob-learning-in-lean-coffee-speed-meet-and-during-breaks">Mob learning in Lean Coffee, Speed Meet and during breaks</h2>
<p>I would say that the best time to learn is whenever you interact and learn as a group.
Breaks and lunch are great opportunities to discuss topics, so we should take advantage of them to improve our knowledge.
I took some time to talk to <a href="https://twitter.com/maaretp">Maaret Pyhäjärvi</a> and discuss some challenges my own team faces along with tactics and strategies to overcome them. I can only say that Maaret provides very insightful tips based on her long time experience in the testing space, working with so many different people. Sometimes we have good ideas but the problem is on how to deliver them; experience is key.
I also had a brief chat with <a href="https://twitter.com/alex_schl">Alex Schladebeck</a> on observability, as we're both somehow newcomers in this space. It's interesting to discuss the doubts, challenges as we start to pave this road.</p>
<p>Speed Meet was quite interesting and cool, as a way to learn a bit about other attendees and what they're interested in but also to learn about ourselves as we have to sum up who we are, what drives us and where we want to go. I found people with great skills, some in speaking, some in automation, some in BDD, well... so many! Unfortunately, I did not have the time to further explore these quick connections, due to lack of time and also because I quickly forgot the names, interests/skills of them. I need to find a way to better handle this in the future.
I had a Lean Coffee session nicely handled by <a href="https://twitter.com/marianneduijst">Marianne Duijst</a> (who made some great <a href="https://twitter.com/i/events/1226815062242885632">sketch notes</a> during ETC by the way), was something kinda new to me. We discussed several topics together and we're able to help each other. It's an interesting way of having discussions around topics that the group wants to, adapt and move to a different topic when the group is ready to.</p>
<h2 id="talks-and-workshops">Talks and workshops</h2>
<p>I already left my feedback on the talks and workshops to the speakers, so I'll take this opportunity just to talk about a very small subset of them.</p>
<p><a href="https://twitter.com/gasparnagy">Gáspár Nagy</a>'s workshop on BDD and Example Mapping was very straight to the point. We did some exercises together and we learned that everyone is able to depict new "rules" (i.e. acceptance criteria) and new examples for them, that would be hard to obtain otherwise. Thus, as a team and with a minimum "structure", it's possible to have a better understanding of the stories/features we aim to deliver. And BDD is all about that. It's curious (or not really, if you understand BDD) that we didn't even touch the "automation part" of it, that sometimes is only where people focus on without understanding the true nature of BDD.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/bdd.jpg" alt="BDD" loading="lazy"></p>
<p><img src="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/bdd_examples.jpg" alt="BDD examples" loading="lazy"></p>
<p>João Proença's talk on deleting "automated tests"/scripts, was also interesting even though we don't pay so much attention to it as we should. Those old, "unknown", crapy automated scripts add pain and costs to your delivery pipeline. So, as your test automation grows, it is crucial to optimize it so you understand what is happening and get short feedback loops, which ultimately are crucial to enable CD.
João will present this same talk at TestBash Brighton 2020, if you're lucky enough to attend it.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/joao_proenca_deleteit.jpg" alt="Joao Proenca on deleting tests" loading="lazy"></p>
<p>Rob Meaney's workshop on testability was very useful. His experience, based on concrete examples, allowed everyone to better understand what is testability about and how we can address it.
I learned about "testing debt" and about ways to make it visible and addressable.
Immediate takeaway: I will apply the testing debt quadrants this week with the team.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/testability.jpg" alt="Testability" loading="lazy"></p>
<h2 id="in-the-end">In the end...</h2>
<p>Well, it was my first and my last EuroTestConf and I feel already a bit sad about it as I loved its format.
At these conferences, you come with a bag of a few friends and you leave with a bag full of them. Even if you make just one connection with whom you can exchange ideas, it will be simply great.
I learned about several topics: testability, BDD, exploratory testing among others. This is great as they were in fact my initial goals.</p>
<p>I took one day off, to "land" my ideas while performing some sightseeing and appreciating Amsterdam and it's beautiful surroundings, including the <a href="https://g.page/museum-muiderslot">Muiderslot castle</a> and <a href="https://www.google.com/maps/place/Zaanse+Schans,+Zaandam,+Netherlands/@52.4777386,4.8173681,15z/data=!3m1!4b1!4m5!3m4!1s0x47c5fcf7bec1145b:0x8b2f4837e2ca20fe!8m2!3d52.472886!4d4.8218542">Zaanse Schans</a>.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/castle.jpg" alt="castle" loading="lazy"></p>
<p><img src="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/windmills.jpg" alt="Zaanse Schans windmills" loading="lazy"></p>
<p>Even though European Testing  Conference has reached its end, the future is bright and everyone will have many opportunities to deepen their testing skills and connect with this amazing community elsewhere.
Thanks again <strong>Maaret Pyhäjärvi</strong> and all the ETC staff for organizing it throughout the years.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/european-testing-conference-my-first-and-last-experience-and-my-takeaways/tester_developer.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[RoboCon 2020: a first impression on Robot Framework gathering]]></title>
            <link>https://www.sergiofreire.com/post/robocon-2020-a-first-impression-on-robot-framework-gathering</link>
            <guid>https://www.sergiofreire.com/post/robocon-2020-a-first-impression-on-robot-framework-gathering</guid>
            <pubDate>Sun, 19 Jan 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>I was lucky enough to attend Robot Framework's annual gathering, the 2020 edition, in Helsinki with the amazing Robot community. In the picture above you may see me together with <a href="https://twitter.com/pekkaklarck">Pekka Klärck</a>, the "father" of Robot Framework; he is a very open person and a great contributor for the whole RF ecosystem.
But before going into the conference details, let me start by talking about a preliminary workshop I had.
I attended a workshop entitled "Advanced SeleniumLibrary and Robot Framework" with <a href="https://github.com/emanlove">Ed Manlove</a>, <a href="https://twitter.com/AaltoTatu">Tatu Aalto</a> and also the collaboration of <a href="https://github.com/Snooz82">René Rohner</a> and <a href="https://github.com/rasjani">Jani Mikkonen</a>. I've not only learned about some advanced topics including extending SeleniumLibrary, but also there were some discussions about broader topics, such as how to manage web locators in an effective and scalable way.</p>
<p>Honestly, I was so glad to see my <a href="https://github.com/robotframework/SeleniumLibrary/pull/1497">humble contribution</a> to SeleniumLibrary during the workshop; glad that it is useful for the community (available from the upcoming v4.2 onwards).</p>
<p><img src="https://www.sergiofreire.com/assets/blog/robocon-2020-a-first-impression-on-robot-framework-gathering/sl_contrib.jpg" alt="Contrib to Selenium Library" loading="lazy"></p>
<p>There were two important messages that I consider to be quite interesting and relevant, especially considering the scope of the workshop :
the fact that the maintainer of SeleniumLibrary project, Tatu Aalto, mentioned that he does very very few GUI based tests in his own case
the general agreement that GUI/selenium based-tests, though interesting, should not be the main focus of testing; concerning automation, API-based tests give a lot more value (of course, on top of unit tests); this requires that testability is built into the product, right from the start
During the workshop, René was very helpful and active, helping keeping it interesting for everyone.</p>
<p>What concerns the event, overall RoboCon was interesting, fun and motivational.
There were many different presentations though, at least this year, I found RPA to have emerged as its own kind of topic (first conference day had many RPA tracks).
E2E, GUI, and microservices testing were also covered in the presentations.
I found out the talk about acceptance testing a real-time audio application (by Akseli Lukkarila, Yousician) fascinating as it depicted a scenario where instead of seeing docker containers you would see wooden boxes as different audio-dedicated test environments.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/robocon-2020-a-first-impression-on-robot-framework-gathering/yousician.jpg" alt="yousician" loading="lazy"></p>
<p>There were also other interesting presentations, covering data-driven testing (René Rohner) to more implementation topics, such as how to implement test automation in a more traditional organization with all typical challenges that we would expect (<a href="https://twitter.com/Anais_vanAsselt">Anaïs van Asselt</a>).
In-between talks, we had a bunch of breaks, giving the opportunity for people to rest, stretch their legs and do some networking.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/robocon-2020-a-first-impression-on-robot-framework-gathering/robocon_hall.jpg" alt="Robocon hall" loading="lazy"></p>
<p>Even though there were people from foreign countries, most attendees were Finnish.
Thus, most conversations in the breaks happened in Finnish (at least my impression); I think this could be improved if the organization promoted English as the main language that should be used (note that all presentations and the workshops were in English).
This would ease people joining on-going conversations, IMO.</p>
<p><img src="https://www.sergiofreire.com/assets/blog/robocon-2020-a-first-impression-on-robot-framework-gathering/robocon_map.jpg" alt="Robocon map" loading="lazy"></p>
<p>I also think that all presentations should have the Twitter handle of the presenter or any other relevant contacts.
But these are just some small things, some nice-to-have.
Talking about improvements, one of the challenges RF seems to yet face is good support for IDEs so they can provide autocomplete thoroughly.
Gladly, we all became aware after Pekka's keynote that showed that this should be addressed right after RF v3.2 is out later this Spring.</p>
<p>But what have I learned?</p>
<p>These are some things that I learned at RoboCon or that were somehow reinforced in the back of my mind:</p>
<ul>
<li>RF is used in the most diverse contexts</li>
<li>there is a vibrant and helpful Robot community, namely in Slack</li>
<li>there may be automation libraries for almost anything and if not, it is probable that someone will make those;  you may find some in this list</li>
<li>some libraries may become deprecated/unmaintained; some care should be taken whenever choosing libraries</li>
<li>open-source means receiving but also giving</li>
</ul>
<p>What concerns more techie things, I wanna try out some libraries afterward, namely:</p>
<ul>
<li><a href="https://pypi.org/project/robotframework-seleniumtestability/">robotframework-seleniumtestability</a></li>
<li><a href="https://github.com/Snooz82/robotframework-datadriver">robotframework-datadriver</a></li>
<li><a href="https://github.com/damies13/rfswarm">RF Swarm</a></li>
<li>among other</li>
</ul>
<p>I want also to explore ways of extending RF and some libraries in the ecosystem.</p>
<p>After RoboCon ends, a kind of call-for-action is somehow triggered in us.
I'm eager to learn more about additional topics, libraries and... who knows... perhaps make some related open-source contributions later on? :)</p>
<p>I still had some last time to do Santa's work :)</p>
<p><img src="https://www.sergiofreire.com/assets/blog/robocon-2020-a-first-impression-on-robot-framework-gathering/santa_sightseeing.jpg" alt="Santa sightseeing" loading="lazy"></p>
<p>See you soon :)
You may find and follow me on <a href="/contact">Twitter and other networks</a>.</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/robocon-2020-a-first-impression-on-robot-framework-gathering/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[A strategy for implementing test automation from scratch in a complex system]]></title>
            <link>https://www.sergiofreire.com/post/a-strategy-for-implementing-test-automation-from-scratch-in-a-complex-system</link>
            <guid>https://www.sergiofreire.com/post/a-strategy-for-implementing-test-automation-from-scratch-in-a-complex-system</guid>
            <pubDate>Tue, 07 Jan 2020 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>Several years ago I worked for a telco company, a more or less R&#x26;D branch of it. From the several roles I had, my latest one was being a "technology consultant". I was part of a centralized team whose focus was processes, trending technologies, tooling, CI/CD, etc.</p>
<p>It was quite interesting because I could try new things and help others. On the other hand, it was also quite challenging because the company was highly hierarchized and most of the teams were working in waterfall, for highly complex products, with a long history and... old technologies.
I loved working there, since there were a bunch of departments working in quite different things: messaging systems, intelligent networks, mobile and TV apps, hardware equipments and more.</p>
<h2 id="the-context">The context</h2>
<p>At a certain point, I was assigned the task of helping an "external" team implementing successfully test automation on fiber optics "solutions" (we'll come back to this).</p>
<p><img src="https://www.sergiofreire.com/assets/blog/a-strategy-for-implementing-test-automation-from-scratch-in-a-complex-system/gpon_overview.jpg" alt="GPON overview" loading="lazy"></p>
<p>To give a bit more background, this team was a testing team, part of a broader testing team that in turn was part of a department. The department had several development teams, mostly hardware related but also software related. Each software team was working in a particular area; some in the CLI (command line interface) of the equipment, some in the management systems, some in the firmware, etc.
When a customer bought a product, they were buying the whole stack of products or a combination of some. Some product existed by themselves while other only while in the context of the whole stack.
To make this even harder, some customers had their own configurations (i.e. variants of the product(s)).</p>
<p>Testing was essentially being done end-to-end, sometimes at the management software level.
Regression testing was being performed manually against each build; as time was scarce, only a subset of tests could be executed; it was simply impossible to execute everything because meanwhile a new build was knocking on the door.</p>
<h2 id="challenges">Challenges</h2>
<p>We had many challenges, technical and non-technical; in hindsight, our biggest challenge was human related which is also the hardest to overcome, IMO.</p>
<p>Some challenges included:</p>
<ul>
<li>lack of communication</li>
<li>testers seen as the end of the line and the ultimate responsible for quality</li>
<li>siloed teams, i.e. teams working separately (devs vs testers, testers vs testers, devs vs devs)</li>
<li>many assumptions</li>
<li>missing or outdated documentation</li>
<li>lack of coding/development know-how</li>
<li>lack of expertise in test automation</li>
<li>no CI/CD</li>
<li>very few automation, except for a few scripts</li>
</ul>
<p>Besides this, our target "solution", or SUT, was too complex and was apparently impossible to automate because we were talking about physical hardware with many physical interconnections, optical and electrical.
Some of these networking equipments are not easy to automate from the outside.</p>
<h2 id="strategy">Strategy</h2>
<p>As we had a strict timeline and we want to show results to all stakeholders (yes, this is probably one of the most important aspects), we needed to be very pragmatic.
Thus, and since the team was very small (4, myself included), we defined clearly the SUT and told everyone that we wouldn't be testing everything; it wasn't viable.
We also stated that this allowed us to do more things in the future and that this part was essential.</p>
<p>We looked at the whole fiber optics system (customer and operator side) and choose the operating system of the main backend/operator side equipment as the main target for testing. We then looked at all the available interfaces and thought from an automation point-of-view: <em>"Can we use this interface to communicate to the equipment?  Can we build an API somehow on top of that? How easy is going to build that API and how stable it will be? What are the cons?"</em>.</p>
<p>What seemed to be impossible at first sight, soon became feasible. We were thinking "hmmm, we can interact with that equipment if we do this or if we use that...".
Building these layers of communication allowed us to build the foundation where we could build our test automation on.</p>
<p>Coding skills were low at the team; no problem, we all can learn. Ruby was chosen as it is a scripted language quite easy to learn; besides, there are a ton of libraries that can easily extend it to do whatever we need. Thus, the team doesn't have to deal with low-level kind of details and may see results sooner.</p>
<p>Cucumber was used to implement the testing scenarios; we were not adopting BDD but we were taking advantage of the Gherkin syntax and the natural ability of having reusable, executable steps across different scenarios.</p>
<p>Since the team was using Jira, we stopped building out reports manually in Word, PDF, Powerpoint, whatever, and sending them by email; instead, we used Jira and proper dashboards that could be accessed even by C-level roles.</p>
<p>Even so, we met on person with all the team members, from the different teams, to show where we were and the gains we were already obtaining.</p>
<p>In sum,</p>
<ul>
<li><strong>simplify our problem, i.e. make our SUT well-defined and remove the moving parts</strong></li>
<li><strong>build APIs if necessary; abstract the hard parts and make them easy to use</strong></li>
<li>focus on regression testing</li>
<li>think on risks and in what risks you're neglecting because your time isn't enough</li>
<li>mentor other teams for "testability"</li>
<li>provide real-time visibility of progress</li>
<li>show what you were not doing before vs what you started to achieve</li>
</ul>
<h2 id="lessons-learned">Lessons learned</h2>
<ul>
<li>automation can be implemented successfully even on teams with few or no coding skills whatsoever</li>
<li>documenting, even if briefly, reusable executable steps/keywords can help providing awareness of what's available</li>
<li>building reusable libraries, with a main responsible/"owner", can motivate and empower people</li>
<li>using Ruby+Cucumber was a good fit; in general, using languages and frameworks where people feel comfortable and that don't require too much bloatware can help achieve results faster and provide that feeling of progress sooner</li>
<li><strong>successful automation requires testability as a main concern from the start</strong></li>
</ul>
<blockquote>
<p>If you're going to implement test automation in on-going project that didn't have testability as one of its concerns from the start, it is going to be hard but it's not impossible.</p>
</blockquote>
<h2 id="in-conclusion">In conclusion</h2>
<p>Even though systems may be complex, people are probably the biggest blocker for automation :)</p>
<p>So, if you're going to implement test automation in an on-going project make sure that (in so special order):</p>
<ul>
<li>you have good communication between everyone (think on ways of promoting this)</li>
<li>that quality is something you all agree on and is everyone's responsibility</li>
<li>everyone is involved in the "test automation project", by sharing goals and tasks</li>
<li>everyone agrees, as much as possible, with the immediate, mid and long-term goals</li>
<li>everyone understands the importance of <strong>testability</strong></li>
</ul>
<p>When the previous points are established, you may proceed with the more technical part:</p>
<ul>
<li>start simple, decompose your problem and focus your automation efforts</li>
<li>if you have parts of your (aim to be) CI pipeline that are not automated, work on those (e.g. making sure the build process is fully automated and have no local dependencies)</li>
<li>don't automate everything or blindly; think from a risk perspective (more on RBT here) and on the repetitive tests/checks that you have to perform on every single build</li>
</ul>
<h2 id="learn-more">Learn more</h2>
<p>To know more about the details, you can watch the following video presentations; they're a bit old though :) Slide deck is also available.
If you want to know more about the challenges we've faced, let me know and I'll be more than glad to go into more details.</p>
<p>Presentation videos follow.</p>
<p><a href="https://youtu.be/VFSAgvljF8Q"><img src="https://img.youtube.com/vi/VFSAgvljF8Q/0.jpg" alt="IMAGE ALT TEXT HERE" loading="lazy"></a></p>
<p><a href="https://youtu.be/_jJrxJ3fiOM"><img src="https://img.youtube.com/vi/_jJrxJ3fiOM/0.jpg" alt="IMAGE ALT TEXT HERE" loading="lazy"></a></p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/a-strategy-for-implementing-test-automation-from-scratch-in-a-complex-system/cover.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Sprint 1 of my website, doing it in Agile way :)]]></title>
            <link>https://www.sergiofreire.com/post/my-website-at-last-in-agile-way</link>
            <guid>https://www.sergiofreire.com/post/my-website-at-last-in-agile-way</guid>
            <pubDate>Thu, 26 Dec 2019 10:00:00 GMT</pubDate>
            <description><![CDATA[<p>Before 2019 ends, it's time to reborn and create my space, my spot to talk about many things, including software testing, Agile and more. Decoration and content will come with time.</p>
<p>Welcome to my website and my first blog post here.
I have been postponing the creation of a website, including owning the domain for quite a while. I wanted to have something cool and elaborate. However, this got delayed, postponed, forgotten. Ever had this kind of feeling in your projects? Probably so, if they suffer from those waterfallish kind of problems.
I aim to improve this site with time, by continuous learning and iterating on it.
Part of my goals include writing here some stuff once in a while.
Topics will include testing, Agile, technology, whatever comes to mind and that may be relevant, at least for some of you.</p>
<p>I hope you enjoy it, feedback is always welcome!</p>
]]></description>
            <enclosure url="https://www.sergiofreire.com/assets/blog/my-website-at-last-in-agile-way/cover.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>