<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Oleksii Lytvynov</title>
    <description>The latest articles on Forem by Oleksii Lytvynov (@alexlitvino).</description>
    <link>https://forem.com/alexlitvino</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2136917%2Fd5924826-442c-466b-8314-5a00f6326da1.png</url>
      <title>Forem: Oleksii Lytvynov</title>
      <link>https://forem.com/alexlitvino</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alexlitvino"/>
    <language>en</language>
    <item>
      <title>The Strange 'else' in Python</title>
      <dc:creator>Oleksii Lytvynov</dc:creator>
      <pubDate>Wed, 01 Jan 2025 18:57:03 +0000</pubDate>
      <link>https://forem.com/alexlitvino/the-strange-else-in-python-40k9</link>
      <guid>https://forem.com/alexlitvino/the-strange-else-in-python-40k9</guid>
      <description>&lt;h3&gt;
  
  
  Else in Conditional Statements
&lt;/h3&gt;

&lt;p&gt;We’ve all written conditional statements and have probably used the complete if-elif-else structure at least once. &lt;br&gt;
For example, when creating a web driver instance for the required browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_browser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chrome&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;firefox&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Firefox&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Browser not supported&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet supports testing with Chrome and Firefox, and raises an exception if an unsupported browser is provided.&lt;/p&gt;

&lt;p&gt;A lesser-known fact is that Python supports the use of the else clause with loops and exception handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Else with Loops
&lt;/h3&gt;

&lt;p&gt;Imagine we have a list of words, and we want to print them as long as they start with an uppercase letter. At the end, we want to check whether all words were processed and, if so, perform specific logic.&lt;/p&gt;

&lt;p&gt;We might use a flag variable &lt;code&gt;is_all_words_processed&lt;/code&gt;, setting it to &lt;code&gt;False&lt;/code&gt; if we encounter an invalid word, then checking it outside the loop to execute the logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;seasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Winter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Spring&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Summer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Autumn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;is_all_words_processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;istitle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;is_all_words_processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_all_words_processed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;All seasons were processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python allows us to avoid the additional variable by placing the logic when all words are valid into the else clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;seasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Winter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Spring&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Summer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Autumn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;istitle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;All seasons were processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The else block will execute only if the loop completes naturally, without a break. If the loop is interrupted by break, the else clause will not run.&lt;br&gt;
Here’s the same example rewritten with a while loop. With while, the else clause behaves in the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;seasons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Winter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Spring&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Summer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Autumn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seasons&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;seasons&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;istitle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seasons&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;All seasons were processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Else in Exception Handling
&lt;/h3&gt;

&lt;p&gt;The else clause can also be used in exception handling. It must come after all except blocks. The code inside the else block will execute only if no exceptions are raised in the try block.&lt;/p&gt;

&lt;p&gt;For example, let’s read a file containing numbers in two columns and print their quotient. We need to handle an invalid file name, while any other errors (e.g., converting a value to a number or division by zero) should cause the program to crash (we will not handle them).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input.dat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Incorrect file name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the try block contains only the code that might raise the caught exception. &lt;br&gt;
&lt;a href="https://docs.python.org/3/tutorial/errors.html#handling-exceptions" rel="noopener noreferrer"&gt;The official documentation&lt;/a&gt; suggests using the else block to avoid unintentionally catching exceptions raised by code outside the try block. Still, the use of else in exception handling might not feel intuitive.&lt;/p&gt;
&lt;h3&gt;
  
  
  Combining Else with Loops and Exception Handling
&lt;/h3&gt;

&lt;p&gt;Here’s an question I posed at interviews. &lt;br&gt;
Suppose we have a &lt;code&gt;Driver&lt;/code&gt; class with a method &lt;code&gt;find_element&lt;/code&gt;. The &lt;code&gt;find_element&lt;/code&gt; method either returns an element or raises an &lt;code&gt;ElementNotFoundException&lt;/code&gt; exception. In this example, it’s implemented to randomly return an element or raise an exception with equal probability.&lt;/p&gt;

&lt;p&gt;Using basic Python syntax, implement a method &lt;code&gt;smart_wait(self, locator: str, timeout: float, step: float)&lt;/code&gt; that checks for an element with the given locator every &lt;code&gt;step&lt;/code&gt; seconds. If the element is found within &lt;code&gt;timeout&lt;/code&gt; seconds, return; otherwise, raise an &lt;code&gt;ElementNotFoundException&lt;/code&gt; exception.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ElementNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Finding element: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ElementNotFoundException&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;smart_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s one approach to implement this method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trying to find the element as long as the timeout hasn't elapsed.&lt;/li&gt;
&lt;li&gt;If the element is found, exit the loop.&lt;/li&gt;
&lt;li&gt;If the element isn’t found, wait for the &lt;code&gt;step&lt;/code&gt; interval.&lt;/li&gt;
&lt;li&gt;Raise an &lt;code&gt;ElementNotFoundException&lt;/code&gt; if the timeout is exceeded.
Here’s a straightforward implementation:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monotonic&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;smart_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;current_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;current_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ElementNotFoundException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;current_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ElementNotFoundException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could shorten the logic a bit by using &lt;code&gt;return&lt;/code&gt; instead of &lt;code&gt;break&lt;/code&gt;, but let's leave it as i for now. &lt;/p&gt;

&lt;p&gt;In fact, this method is implemented in the WebDriverWait class of Selenium - &lt;a href="https://github.com/SeleniumHQ/selenium/blob/e7f55240a307d956e8ac502886541de38a525126/py/selenium/webdriver/support/wait.py#L83" rel="noopener noreferrer"&gt;until&lt;/a&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;POLL_FREQUENCY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;  &lt;span class="c1"&gt;# How long to sleep in between calls to the method
&lt;/span&gt;&lt;span class="n"&gt;IGNORED_EXCEPTIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NoSuchElementException&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;  &lt;span class="c1"&gt;# default to be ignored.
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;poll_frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;POLL_FREQUENCY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ignored_exceptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WaitExcTypes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Calls the method provided with the driver as an argument until the &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s"&gt;        return value does not evaluate to ``False``.

        :param method: callable(WebDriver)
        :param message: optional message for :exc:`TimeoutException`
        :returns: the result of the last call to `method`
        :raises: :exc:`selenium.common.exceptions.TimeoutException` if timeout occurs
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;stacktrace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_timeout&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_driver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_ignored_exceptions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;screen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;stacktrace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stacktrace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;end_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_poll&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stacktrace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s rewrite this method using else for both exception handling and loops:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exception could be raised only in line &lt;code&gt;self.find_element(locator)&lt;/code&gt;. Exit from loop should be performed in case when exception wasn't raised. So we could move &lt;code&gt;break&lt;/code&gt; to else block.&lt;/li&gt;
&lt;li&gt;Our method should raise exception if loop was exited not because of break. So we could move exception raising to else clause of the loop.
&lt;/li&gt;
&lt;li&gt;If you perform transformation 1 and 2 consequentially, you see that current time could be taken only in loop condition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Completing these transformations, we obtain a method that uses the else statement for both exception handling and the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monotonic&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;smart_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ElementNotFoundException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ElementNotFoundException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;What can I say... This is one of Python’s lesser-known features. Infrequent use might make it less intuitive to use in every scenario — it can lead to confusion. However, knowing it and applying it effectively when needed is undoubtedly worthwhile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy New Year!&lt;/strong&gt; 🎉🎄🎅&lt;/p&gt;

&lt;p&gt;P.S. It was really scary 😱:&lt;br&gt;
I write articles on my own but translate them using ChatGPT. For translation I removed all code snippets but ChatGPT restores them all 👻&lt;/p&gt;

</description>
      <category>python</category>
      <category>development</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>No Country For \0 (Escaping Characters Issues in ReportPortal)</title>
      <dc:creator>Oleksii Lytvynov</dc:creator>
      <pubDate>Sun, 01 Dec 2024 17:35:00 +0000</pubDate>
      <link>https://forem.com/alexlitvino/no-country-for-0-escaping-characters-issues-in-reportportal-2c6p</link>
      <guid>https://forem.com/alexlitvino/no-country-for-0-escaping-characters-issues-in-reportportal-2c6p</guid>
      <description>&lt;p&gt;This is another story that happened to me during the integration of tests with ReportPortal. ReportPortal allows uploading a test description stored in the test function’s docstring. This is pretty convenient: test cases are stored along with the scripts and are also published together with the results.&lt;br&gt;
Let’s consider a few cases. &lt;/p&gt;
&lt;h3&gt;
  
  
  Test with plain docstrings
&lt;/h3&gt;

&lt;p&gt;We can use plain text to describe what the test verifies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_usual_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify adding integer positive numbers

    First number is 3
    Second number is 5

    Result is 8
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the result we get on the test launch page in ReportPortal:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwdtyklzxeq00dsvyh5f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwdtyklzxeq00dsvyh5f.png" alt="Results with plain text" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Empty lines are not displayed. But if you go to the test details page, the description will match the docstring:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyyjfkyg36azvedyclyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyyjfkyg36azvedyclyv.png" alt="Results with plain text in test details" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Test with a docstrings having an indent in the first line and an empty line at the end
&lt;/h3&gt;

&lt;p&gt;Docstrings in Python are described in PEP 257 – Docstring Conventions. According to &lt;a href="https://peps.python.org/pep-0257/#handling-docstring-indentation" rel="noopener noreferrer"&gt;it&lt;/a&gt;, any leading spaces in the first line and blank lines at the beginning and end of docstrings should be stripped by tools that creates documentation. This PEP includes a &lt;code&gt;trim&lt;/code&gt; function that processes docstrings according to these rules. The pytest_reportportal library has the same &lt;code&gt;trim_docstring&lt;/code&gt; &lt;a href="https://github.com/reportportal/agent-python-pytest/blob/36f30273788512d19257f29b2d4b812b782efd0b/pytest_reportportal/service.py#L66" rel="noopener noreferrer"&gt;function&lt;/a&gt;, taken from PEP without modifications.&lt;/p&gt;

&lt;p&gt;If we use a docstring with an indent in the first line and an empty line at the end, they will not appear in ReportPortal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_indent_in_first_line_and_empty_line_at_the_end_of_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;     Verify adding integer positive numbers

    First number is 4
    Second number is 6

    Result is 10

    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nfnq2gxh90naseb3xhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nfnq2gxh90naseb3xhs.png" alt="Doctring with indent in first line and empty line in the end" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5p2dd081l3ykqf6pgdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5p2dd081l3ykqf6pgdt.png" alt="Results with indented first line in test details" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test with Markdown in docstrings
&lt;/h3&gt;

&lt;p&gt;On the test details page, there are elements for editing test description. If we check the ReportPortal service-ui subproject responsible for rendering results on the web, we'll find components &lt;a href="https://github.com/reportportal/service-ui/tree/develop/app/src/components/main/markdown" rel="noopener noreferrer"&gt;markdownEditor and markdownViewer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s create a test with Markdown in the docstring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_markdown_in_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;**Verify adding float positive numbers**

    - First number is 1.73
    - Second number is 3.1
    ---
    Result is 4.83
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result in ReportPortal:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg56t5d91dhwd1eeag2ss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg56t5d91dhwd1eeag2ss.png" alt="Docstring with Markdown" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first line is bolded, the second and third lines are listed as bullet points, and the result is separated by a horizontal line. On the web page, the formatting looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"markdownViewer__markdown-viewer--GikqC mode-default"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Verify adding float positive numbers&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;First number is 1.73&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Second number is 3.1&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Result is 4.83&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test with \t and \n in docstrings
&lt;/h3&gt;

&lt;p&gt;On the work, we often use strings with \t and \n as test strings. For example, something like this test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_tab_and_newline_in_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify application processes lines with &lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt; and &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;

    Add line &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt;456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
    Add line &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;789&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;012&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

    Printed lines equal to entered lines
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn’t contain Markdown formatting, but the result is unexpected:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd579c6qvp8cyebl41spr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd579c6qvp8cyebl41spr.png" alt="Docstrings with non-escaped \n and \t" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we examine the markup, we find two code blocks (&lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"markdownViewer__markdown-viewer--GikqC mode-default"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Verify application processes lines with          and&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;
        Add line "123       456"
        Add line "789
    &lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;012"&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;Printed lines equal to entered lines&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then checked how the test description was sent in requests to ReportPortal (with and without special characters) using Wireshark and noted when the text was treated as a code block in Markdown. It turns out that we publish test with the following docstrings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_tab_and_newline_in_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify application processes lines with &lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt; and &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;

    Add line &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt;456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
    Add line &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;789
012&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

    Printed lines equal to entered lines
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue with the indent lies in the &lt;code&gt;trim_docstring&lt;/code&gt; function. It has a fragment that calculates the minimum indent in the docstring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Determine minimum indentation (first line doesn't count):
&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxsize&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
    &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;indent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because of the newline character, the line 012" starts at the beginning of line, without an indent. Because of that, lines indented by four spaces are treated as code blocks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By the way, this function was the first real production code where I saw the usage of the expandtabs string method. Initially, all tabs in the docstring are replaced with 8 spaces, then the string is split into lines: &lt;/p&gt;

&lt;p&gt;lines = docstring.expandtabs().splitlines()&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This issue can be fixed by escaping special characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_escaped_tab_and_newline_in_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify application processes lines with &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;t and &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n

    Add line &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;t456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
    Add line &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;789&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n012&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

    Printed lines equal to entered lines
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us the expected result:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkghk998h6twhmntjd2l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkghk998h6twhmntjd2l.png" alt="Docstrings with escaped \n and \t" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Test with \0 in docstrings
&lt;/h3&gt;

&lt;p&gt;We also have test strings containing the \0 character. This caused a separate issue. When publishing results in such cases, the test launch is created, but the results are not published.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_slash_zero_in_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify application processes lines with &lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s"&gt;

    Add line with &lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s"&gt;

    Printed lines equal to entered lines
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, checking the requests sent to ReportPortal, we see that the &lt;code&gt;description&lt;/code&gt; field contains \0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"codeRef"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test_example.py:test_with_slash_zero_in_docstrings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify application processes lines with &lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;0000&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Add line with &lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;0000&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Printed lines equal to entered lines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"hasStats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reportportal_escaping/test_example.py::test_with_slash_zero_in_docstrings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"retry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"retryOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"startTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1732975747211"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"testCaseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test_example.py:test_with_slash_zero_in_docstrings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STEP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"launchUuid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5d3d8726-a58f-47de-bd85-1e21353a39a7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This issue seems doesn't belong to pytest_reportportal library but further down the stack. Perhaps it relates to request parsing or &lt;code&gt;description&lt;/code&gt; field rendering on the web UI. I reported this problem in the pytest_reportportal library issue tracker (but maybe it should be moved to other subproject): &lt;br&gt;
 &lt;a href="https://github.com/reportportal/agent-python-pytest/issues/382" rel="noopener noreferrer"&gt;Test not published to Report Portal when docstring or parameter contains \0 #38&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also can be resolved by escaping the special character:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_escaped_slash_zero_in_docstrings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify application processes lines with &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;0

    Add line with &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;0

    Printed lines equal to entered lines
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8aovjmkbravh2wmm09zc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8aovjmkbravh2wmm09zc.png" alt="Docstrings with escaped \0" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test with \0 in a parameter value of parametrized test
&lt;/h3&gt;

&lt;p&gt;A bigger problem arises if a parameterized test contains \0 in its parameter value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_slash_zero_in_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the request sent to create the test, the &lt;code&gt;name&lt;/code&gt; field contains the escaped \0, but the &lt;code&gt;testCaseId&lt;/code&gt; field contains the unescaped value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reportportal_escaping/test_example.py::test_with_slash_zero_in_params[abc&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;x00]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="nl"&gt;"testCaseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test_example.py:test_with_slash_zero_in_params[abc&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;0000]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This value cannot be escaped because the test requires it in its original form. As a workaround, we used a placeholder string for the parameter to indicate that the test string contains \0, and in the test itself, we replaced it with the actual value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line with slash zero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_slash_zero_in_params_workaround&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line with slash zero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# test logic that uses variable line
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;ReportPortal is a widely used system for storing and processing test results. It offers advanced features like AI for result analysis. However, as we can see, even such system has shortcomings in rare use cases.&lt;/p&gt;

&lt;p&gt;======================================================&lt;br&gt;
[Update, 04 Dec 2024]&lt;/p&gt;

&lt;p&gt;Contributor @HardNorth replied regarding this issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;\0 is not a text character, it is strictly binary one. So strictly, that many libraries use it to check if data is binary or text. And RP wasn't supposed to work with binary data. Also in general we do not modify data on client side, except some special cases, so it's your responsibility to sanitize it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nevertheless, he fixed issue with non-publishing results in cases parameter value contains \0 (now it will be escaped, but only for parameters). It works starting from pytest-reportportal 5.4.7 👍&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7azcoztlq0ktrn3ru5f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7azcoztlq0ktrn3ru5f.png" alt="Test with \0 in parameter value" width="800" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>reportportal</category>
      <category>testing</category>
      <category>testautomation</category>
      <category>pytest</category>
    </item>
    <item>
      <title>How ReportPortal "Made" Pytest Run Twice</title>
      <dc:creator>Oleksii Lytvynov</dc:creator>
      <pubDate>Sun, 24 Nov 2024 19:44:15 +0000</pubDate>
      <link>https://forem.com/alexlitvino/how-reportportal-made-pytest-run-twice-2lfp</link>
      <guid>https://forem.com/alexlitvino/how-reportportal-made-pytest-run-twice-2lfp</guid>
      <description>&lt;h3&gt;
  
  
  A story of how a coincidence led to an unexpected behavior
&lt;/h3&gt;

&lt;p&gt;We used to store our test run results in TestRail, but the time came to switch to something else. Interestingly, this isn’t the first time I’ve seen teams move away from TestRail recently. We considered various options for storing test results and eventually chose ReportPortal. Later we could talk about other options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating ReportPortal with Pytest
&lt;/h3&gt;

&lt;p&gt;What surprised me - the integration was very simple. Unlike TestRail, which required writing an API client, publishing results to ReportPortal only requires specifying a few command-line options. &lt;/p&gt;

&lt;p&gt;As you might guess from the title, the tests are written in Pytest. To publish results to ReportPortal, you just need to include the following options to the command that runs tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;--reportportal: enable publishing&lt;/li&gt;
&lt;li&gt;--rp-endpoint: the address of your ReportPortal server&lt;/li&gt;
&lt;li&gt;--rp-api-key: your API key, generated in the User Profile section of ReportPortal&lt;/li&gt;
&lt;li&gt;--rp-project: the name of the project where results should be published&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, to run all tests and publish results to ReportPortal, we could use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nt"&gt;--reportportal&lt;/span&gt; &lt;span class="nt"&gt;--rp-endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;RP_SERVER_ADDRESS&amp;gt; &lt;span class="nt"&gt;--rp-api-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;RP_API_KEY&amp;gt; &lt;span class="nt"&gt;--rp-project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;PROJECT_NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run tests with results publishing from a local machine, then we can define these parameters in pytest.ini file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[pytest]&lt;/span&gt;
&lt;span class="py"&gt;rp_endpoint&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;RP_SERVER_ADDRESS&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;rp_api_key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;RP_API_KEY&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;rp_project&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;PROJECT_NAME&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ReportPortal provides additional options as well. For example, &lt;code&gt;--rp-rerun&lt;/code&gt; to overwrite results in an existing launch or &lt;code&gt;--rp-launch-description&lt;/code&gt; to add a description to the test launch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that command line options use hyphens(-), while pytest.ini parameters use underscores (_)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We publish results during the regression run when the final build is ready. Before that, regression could be run when tests were updated but without results being recorded. Regression is executed using GitLab CI/CD on dedicated machines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding GitLab CI/CD Integration
&lt;/h3&gt;

&lt;p&gt;Next we need to integrate result publishing with the GitLab CI/CD pipeline. Some runs would include results publishing, while others - not.&lt;/p&gt;

&lt;p&gt;We add a dropdown variable to define whether publish results or not to the &lt;code&gt;variables&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IS_RP_RUN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Select&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'yes'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Report&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Portal"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we get on GitLab CI/CD UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferwjq86yc4if25tyuh1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferwjq86yc4if25tyuh1a.png" alt="Dropdown on GitLab CI/CD UI" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitLab CI/CD supports text variables and dropdowns. Dropdowns are useful for predefined options like enabling/disabling results publishing or selecting OS/browser to run tests on&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default, I choose value "no," as results should only be published for debugged tests. Depending on &lt;code&gt;IS_RP_RUN&lt;/code&gt; value, we could pass or not pass options required for results publishing&lt;/p&gt;

&lt;p&gt;The following conditional logic is added to the &lt;code&gt;rules&lt;/code&gt; section of job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$IS_RP_RUN == "no"&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;RP_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$IS_RP_RUN == "yes"&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;RP_OPTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--reportportal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--rp-endpoint=&amp;lt;RP_SERVER_ADDRESS&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--rp-api-key=&amp;lt;RP_API_KEY&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--rp-project=&amp;lt;PROJECT_NAME&amp;gt;'&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To the &lt;code&gt;script&lt;/code&gt; section, we add command to run all tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nv"&gt;$RP_OPTIONS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Condition above will pass parameters for results publishing if we select "yes". If we don't want to publish results, an empty string will be passed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In fact, with specified rules, pipeline will run on every commit. You could add one more variable for the build version. Then update the condition, so tests will run only when the version is specified. Or you could add condition "not to run pipeline on push events"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's run pipeline and see what we get in ReportPortal (Run ID is 23 because I ran it several time before).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxjhit0fn03lnq6upkg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxjhit0fn03lnq6upkg9.png" alt="ReportPortal test launch" width="800" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We may need to pass other parameters for ReportPortal. So we create an additional text variable &lt;code&gt;RP_ADDITIONAL_OPTIONS&lt;/code&gt; for them. It will have an empty string as a default value. In case when we run tests with the results publishing, we could enter there additional parameters.&lt;br&gt;
Then the test run command will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nv"&gt;$RP_OPTIONS&lt;/span&gt; &lt;span class="nv"&gt;$RP_ADDITIONAL_OPTIONS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's run tests again with the &lt;code&gt;--rp-rerun&lt;/code&gt; option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fol4ozo9456siz9ohs876.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fol4ozo9456siz9ohs876.png" alt="GitLab CI/CD UI with text variable" width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ReportPortal will successfully show that tests were rerun (Rerun mark appears on the same 23rd test launch):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwj9ckxbtuhxkjzryq7uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwj9ckxbtuhxkjzryq7uq.png" alt="ReportPortal test launch with Rerun label" width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I want to add a description to the test launch, I should specify the option &lt;code&gt;--rp-launch-description="Experimental run"&lt;/code&gt;. But then the first problem will occur - the description will only indicate "Experimental and tests will not run at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxddz1fq0s93euqlsw07p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxddz1fq0s93euqlsw07p.png" alt="ReportPortal test launch with cut description" width="800" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem occurs because the expanded variable will be split by whitespace. Then the second part of the launch description (run") will be considered as the next parameter - test module, PyTest would like to run, but can't identify.&lt;br&gt;
It is easy to fix that - just quote the variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nv"&gt;$RP_OPTIONS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RP_ADDITIONAL_OPTIONS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what we get now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplr44fiypmmhbx8a38in.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplr44fiypmmhbx8a38in.png" alt="ReportPortal test launch with full description" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are already approaching a "double" run:) It is not always necessary to run all tests. While we are debugging tests, we may want to run a separate module. To do this, we could add another variable &lt;code&gt;MODULE_TO_RUN&lt;/code&gt;. We will specify the name of the module with tests in it. To run all tests, we will set &lt;code&gt;test_*.py&lt;/code&gt; by default (let's say in this project tests are organized only by modules).&lt;br&gt;
Then command to run tests will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nv"&gt;$RP_OPTIONS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RP_ADDITIONAL_OPTIONS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; MODULE_TO_RUN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ...And now double run
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I should mention straight away that this issue is reproduced only on Linux&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While I was testing run with additional ReportPortal options, everything was fine. Also, while I was adding integration with ReportPortal, I ran a module with only one test function. Now it's time to run tests as in real battle - all tests and without additional ReportPortal options.&lt;/p&gt;

&lt;p&gt;Without reporting, the runtime of all tests was half an hour.&lt;br&gt;
And I began to wait.&lt;br&gt;
And wait.&lt;br&gt;
And wait some more...&lt;/p&gt;

&lt;p&gt;But half an hour had already passed. All the tests already appeared in ReportPortal (it publishes the results after each test function, so you see the results immediately). And now more tests began to appear in ReportPortal - more than there were in the project!&lt;/p&gt;

&lt;p&gt;Let's look at the command we start tests this time, substituting all the values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nt"&gt;--reportportal&lt;/span&gt; &lt;span class="nt"&gt;--rp-endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;RP_SERVER_ADDRESS&amp;gt; &lt;span class="nt"&gt;--rp-api-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;RP_API_KEY&amp;gt; &lt;span class="nt"&gt;--rp-project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;PROJECT_NAME&amp;gt; &lt;span class="s2"&gt;""&lt;/span&gt; test_&lt;span class="k"&gt;*&lt;/span&gt;.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There seems to be nothing criminal. The only thing that can be alerting here is the empty string in quotes. Let's search for "pytest runs tests twice when contains quotes" and the second search result we get is an issue in the PyTest bug tracker: &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://github.com/pytest-dev/pytest/issues/7012" rel="noopener noreferrer"&gt;Quotation marks in pytest command collects duplicate tests #7012&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;In the defect description we can see our case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nt"&gt;--collect-only&lt;/span&gt; tests.py &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you read the details, you will find out that an empty string in quotes is interpreted by PyTest as LocalPath (.) and tests are also duplicated by the expression &lt;code&gt;test_*.py&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This problem is not yet solved in PyTest. So how can we fix it on our side? We need some value in the &lt;code&gt;RP_ADDITIONAL_OPTIONS&lt;/code&gt; variable.&lt;br&gt;
Let's  look at the list of ReportPortal options and choose some option with a default value. For example, &lt;code&gt;--rp-mode=DEFAULT&lt;/code&gt;. Running the tests now, we will see that they are run only once.&lt;/p&gt;




&lt;p&gt;ReportPortal offers many features for improving regression runs. Though I didn't find everything I wanted (for example grouping tests by modules and packages). Also, I faced one more interesting peculiarity of ReportPortal. I'll tell you about it some other time&lt;/p&gt;

</description>
      <category>pytest</category>
      <category>reportportal</category>
      <category>testing</category>
      <category>testautomation</category>
    </item>
    <item>
      <title>Regular Expressions for Highlighting Comments in PyCharm</title>
      <dc:creator>Oleksii Lytvynov</dc:creator>
      <pubDate>Sun, 17 Nov 2024 15:47:11 +0000</pubDate>
      <link>https://forem.com/alexlitvino/regular-expressions-for-highlighting-comments-in-pycharm-8p3</link>
      <guid>https://forem.com/alexlitvino/regular-expressions-for-highlighting-comments-in-pycharm-8p3</guid>
      <description>&lt;p&gt;Students often ask why regular expressions are necessary. At first glance, their usefulness may not be obvious. In general, their purpose is working with text: searching and replacing.&lt;/p&gt;

&lt;p&gt;For instance, I once needed to compare logs from two test runs. They were potentially identical, but each line began with timestamps that differed.&lt;br&gt;
Using a regular expression to match the timestamps, I replaced those substrings with an empty string in Sublime (a feature likely available in any text editor). Afterward, I compared the two files in Meld — a handy tool for comparing files and directories that I frequently use.&lt;/p&gt;

&lt;p&gt;PyCharm also supports searching and replacing text with regular expressions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2q0qlb5dcgg1ydzmjx0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2q0qlb5dcgg1ydzmjx0.png" alt="PyCharm Search" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, I’ll discuss another PyCharm feature that utilizes regular expressions: highlighting specific comments in code. The most common example is TODO comments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3y41yo4qiqa1kma5liq.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3y41yo4qiqa1kma5liq.PNG" alt="TODO comment" width="800" height="58"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But how does PyCharm know to highlight this text in a specific color? These settings can be customized, and that’s what we’ll explore.&lt;/p&gt;

&lt;p&gt;Navigate to the menu:&lt;br&gt;
File -&amp;gt; Settings... -&amp;gt; Editor -&amp;gt; TODO&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftwo10ufmxftvjkc8pdp.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftwo10ufmxftvjkc8pdp.PNG" alt="TODO settings" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you'll find two predefined rules for highlighting comments: TODO and FIXME. Try it — FIXME uses the same highlighting rule as TODO.&lt;br&gt;
The rule itself is defined by the regular expression: &lt;code&gt;\btodo\b.*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This pattern matches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The word &lt;code&gt;todo&lt;/code&gt; as a whole word (using &lt;code&gt;\b&lt;/code&gt; to denote a word boundary)&lt;/li&gt;
&lt;li&gt;Followed by any number of any characters (&lt;code&gt;.*&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A word boundary (&lt;code&gt;\b&lt;/code&gt;) is the edge between a &lt;code&gt;\w&lt;/code&gt; character (letters, digits, or underscores) and a non-&lt;code&gt;\w&lt;/code&gt; character.&lt;/p&gt;

&lt;p&gt;In this menu, you can add your own rules. For example, let’s add a rule for "Not implemented".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7b3o7glg200237rk6lu.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7b3o7glg200237rk6lu.PNG" alt="Adding Not implemented rule" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you can configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pattern: a regular expression to match the text to highlight.&lt;/li&gt;
&lt;li&gt;Icon: an icon to display in the TODO tool window.&lt;/li&gt;
&lt;li&gt;Case sensitivity: whether the match is case-sensitive.&lt;/li&gt;
&lt;li&gt;Default style: if unchecked, you can customize the highlighting style, otherwise default TODO style will be applied.&lt;/li&gt;
&lt;li&gt;Custom style options:

&lt;ul&gt;
&lt;li&gt;Font style (bold, italic)&lt;/li&gt;
&lt;li&gt;Text color&lt;/li&gt;
&lt;li&gt;Background color&lt;/li&gt;
&lt;li&gt;Error stripe color&lt;/li&gt;
&lt;li&gt;Decoration styles and their colors (underscored, bold underscored, underwaved, bordered, strikeout, dotted line).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Here’s what we’ve got:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qq92vvj5d2jo00bkpuq.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qq92vvj5d2jo00bkpuq.PNG" alt="Not implemented comment" width="800" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s how it appears in the TODO tool window: you can see the custom icon we selected, and the border color is more prominent. In this screenshot, I’ve also clicked the filters button to show that no filters are applied yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyf2h2v5yt5bk6spajff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyf2h2v5yt5bk6spajff.png" alt="TODO tool window" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s return to the TODO settings and add a filter for the "Not implemented" rule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lk08vbhedhmoyjt0vfn.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lk08vbhedhmoyjt0vfn.PNG" alt="Adding Not implemented filter" width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, in the TODO tool window, a new "Not implemented" filter appears in the list. When you select this filter, only "Not implemented" comments will display in the TODO tool window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf87gj1fe7eij8ysblax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf87gj1fe7eij8ysblax.png" alt="TODO tool window with filter applied" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s one more option in the TODO comment settings we haven’t mentioned: "Treat indented text on the following lines as part of the same TODO".&lt;/p&gt;

&lt;p&gt;This checkbox applies to all rules. If the line following a TODO comment contains an indented comment, the same rule will be applied to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnrpth0ht24ae93l1tqxd.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnrpth0ht24ae93l1tqxd.PNG" alt="TODO comment with indented text" width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;As you can see, there’s no need to create overly complex regular expressions — simple ones will suffice. However, even in this context, they can make your work easier.&lt;br&gt;
Do you use regular expressions often?&lt;/p&gt;

</description>
      <category>python</category>
      <category>pycharm</category>
      <category>regex</category>
      <category>tricks</category>
    </item>
    <item>
      <title>How to "serve" portfolio for test engineer</title>
      <dc:creator>Oleksii Lytvynov</dc:creator>
      <pubDate>Sun, 10 Nov 2024 21:49:03 +0000</pubDate>
      <link>https://forem.com/alexlitvino/how-to-serve-portfolio-for-test-engineer-4fpi</link>
      <guid>https://forem.com/alexlitvino/how-to-serve-portfolio-for-test-engineer-4fpi</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Open to work, open to work, open to work...&lt;br&gt;
Some people search for a job for a long time, while others are luckier. But luck alone won’t help much. According to statistics of Djinni service, there are &lt;strong&gt;83,629&lt;/strong&gt; candidates and only &lt;strong&gt;7,357&lt;/strong&gt; open positions. It's time for despair, right?&lt;/p&gt;

&lt;p&gt;Common advice for increasing your chances includes crafting a solid CV and a well-organized LinkedIn profile. Another tip is to create a portfolio.&lt;/p&gt;

&lt;p&gt;Will it be reviewed? When I hired employees, I checked GitHub links in CVs when there was one. In a large pool of candidates, a portfolio can be that "spark" that makes you stand out. Even if it doesn’t get reviewed, building a portfolio gives you valuable experience that will be useful in your work.&lt;br&gt;
However, if you’re showing a portfolio, it’s worth preparing it well.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One candidate had a GitHub link in his CV. When I followed it, I found a couple of projects with several files containing "Hello, world!". It didn’t leave a good impression.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Portfolio for a Manual QA Specialist
&lt;/h2&gt;

&lt;p&gt;I don’t really support the strict division between manual testers and automation specialists. While some companies have separate team of manual testers who write test cases and automation team to automate them, in my experience, such companies are rare. If a QA chooses only one stack, they limit their job options. I often worked on projects from requirements analysis to the final release.&lt;/p&gt;

&lt;p&gt;The outcome of a manual tester’s work is a set of testing artifacts. They should be clear, have enough coverage, and be prioritized (don't forget to start with the happy path).&lt;/p&gt;

&lt;p&gt;As a manual QA, you can create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requirements to product (SRS) (keeping in mind quality criteria like completeness, clarity, consistency, necessity, feasibility, and testability)&lt;/li&gt;
&lt;li&gt;test plan (you could follow IEEE 829, but concise documentation – one page – is better for a portfolio. In your day-to-day work include only necessary sections as well. Focus on a couple of features rather than trying to cover the entire product. Be sure to state what you’re testing and what you’re not).&lt;/li&gt;
&lt;li&gt;checklists&lt;/li&gt;
&lt;li&gt;test cases&lt;/li&gt;
&lt;li&gt;test execution report and, if you’re lucky, a bug report&lt;/li&gt;
&lt;li&gt;test report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What to test? For simplicity, you could choose well-known programs, like Notepad, MS Word, or Google Spreadsheets. If you’re applying to a company focused on mobile app development, it’s worth choosing a mobile app since mobile testing has its own peculiarities. You can also prepare testing documentation for different types of products (desktop, web, mobile).&lt;/p&gt;

&lt;p&gt;You can also include examples of your work created during a hackathons or competitions (e.g., &lt;a href="https://uk.devchallenge.it/" rel="noopener noreferrer"&gt;Dev Challenge&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Portfolio for a Test Automation Specialist
&lt;/h2&gt;

&lt;p&gt;I’d recommend starting the same way as a manual tester. Decide what you will test – web, mobile, or API – or create automated tests for different types of projects. Your code quality as much important as quality for manual testing artifacts. A few automated scripts in a "blanket" style won’t reflect well on you. Instead, think about building a basic framework that’s well-structured, easy to expand, and follows code conventions. Potential employers may want to know your coding process – whether you pushed all changes to main or followed GitHub Flow. Don’t forget to write a good README – that’s likely to be the first file someone checks. Describe your framework’s structure and provide instructions for running tests.&lt;/p&gt;

&lt;p&gt;In addition to automating different types of projects, you can try various approaches (examples for Python in parentheses):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modular framework (pytest)&lt;/li&gt;
&lt;li&gt;BDD (pytest-bdd, behave)&lt;/li&gt;
&lt;li&gt;KDT (RobotFramework)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For writing automated tests, you can use the following resources:&lt;/p&gt;

&lt;h4&gt;
  
  
  Web UI
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://demo.guru99.com/Agile_Project/Agi_V1/" rel="noopener noreferrer"&gt;https://demo.guru99.com/Agile_Project/Agi_V1/&lt;/a&gt; - very simple (basically login and a few actions)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.globalsqa.com/angularJs-protractor/BankingProject/#/login" rel="noopener noreferrer"&gt;https://www.globalsqa.com/angularJs-protractor/BankingProject/#/login&lt;/a&gt; - more interactive&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.saucedemo.com/" rel="noopener noreferrer"&gt;https://www.saucedemo.com/&lt;/a&gt; - test site from Sauce Labs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opensource-demo.orangehrmlive.com/web/index.php/auth/login" rel="noopener noreferrer"&gt;https://opensource-demo.orangehrmlive.com/web/index.php/auth/login&lt;/a&gt; - demo site from OrangeHRM. Once I was asked to automate several tests for this site as a job test assignment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  REST API
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://petstore.swagger.io/" rel="noopener noreferrer"&gt;https://petstore.swagger.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gorest.co.in/" rel="noopener noreferrer"&gt;https://gorest.co.in/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://restful-booker.herokuapp.com/" rel="noopener noreferrer"&gt;http://restful-booker.herokuapp.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jsonplaceholder.typicode.com/" rel="noopener noreferrer"&gt;https://jsonplaceholder.typicode.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dummy.restapiexample.com/" rel="noopener noreferrer"&gt;https://dummy.restapiexample.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to Publish Your Portfolio
&lt;/h2&gt;

&lt;p&gt;You can store your manual testing documentation on Google Drive (just remember to set view permissions). But it’s better to upload your work to GitHub. Even if you’re applying for a manual testing role, knowing Git will be an advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;Some sources suggest asking a project manager for permission to share work samples from your previous projects. I doubt this is common practice due to NDAs. However, you don't need such permission if you write open-source code. Open-source projects also require tests, and you might fix a bug as well.&lt;/p&gt;

&lt;p&gt;For example, one candidate showcased code she had written for open-source during working tasks.&lt;br&gt;
While not many companies contribute to open-source projects, you can contribute personally and show examples of your code that were accepted in an open-source project. This is potentially more impressive than personal projects because it shows you worked with someone else’s code, followed code conventions, wrote tests, etc.&lt;/p&gt;

&lt;p&gt;It’s best to focus on a single project and make several contributions to understand it better. Constantly switching between projects can reduce efficiency. For a given project, you can link to your accepted pull requests. For example, here are my accepted &lt;a href="https://github.com/joke2k/faker/pulls?q=is%3Apr+author%3AAlexLitvino+is%3Aclosed" rel="noopener noreferrer"&gt;pull requests&lt;/a&gt; for the Faker library.&lt;/p&gt;




&lt;p&gt;I wish you success in your job search if you’re currently looking. Try not to waste time. If you’re in between jobs, use this time wisely.&lt;/p&gt;

</description>
      <category>career</category>
      <category>testing</category>
      <category>qa</category>
      <category>softwaretesting</category>
    </item>
  </channel>
</rss>
