<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>QF-Test - blog</title><link href="https://www.qftest.com/en/" rel="alternate"/><link href="https://www.qftest.com/blog.atom.xml" rel="self"/><id>https://www.qftest.com/en/</id><updated>2026-04-01T00:00:00+02:00</updated><entry><title>Using GitHub Copilot CLI via Remote SSH from QF-Test</title><link href="https://www.qftest.com/en/blog/article/llm-copilot-ssh-remote.html" rel="alternate"/><published>2026-04-01T00:00:00+02:00</published><updated>2026-04-01T00:00:00+02:00</updated><author><name>Irfan Yigit, ASC Technologies</name></author><id>tag:www.qftest.com,2026-04-01:/en/blog/article/llm-copilot-ssh-remote.html</id><summary type="html">&lt;p&gt;This quick guide explains how to use the &lt;strong&gt;GitHub Copilot CLI&lt;/strong&gt; via a remote SSH connection directly from &lt;strong&gt;QF-Test&lt;/strong&gt; — including SSH key setup and troubleshooting.&lt;/p&gt;</summary><content type="html">&lt;p&gt;With QF-Test 10, it is possible to &lt;a href="/en/blog/article/llm-copilot.html"&gt;integrate third-party AI models such as GitHub Copilot into QF-Test.&lt;/a&gt; In the case of Copilot, it is usually necessary to have the Copilot CLI installed on the same system as QF-Test.&lt;/p&gt;
&lt;p&gt;However, there are situations where you cannot install the Copilot CLI on your test machines for technical or security reasons, or if you want to run multiple parallel QF-Test instances via &lt;a href="www.qftest.com/doc/manual/en/tech_daemon_api.html"&gt;Daemon mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For such cases, this guest article by long-time QF-Test user Irfan Yigit shows you how to set up Copilot on a single central machine and establish an SSH network connection to your test machines. You can then use your GitHub Copilot AI models with QF-Test via SSH.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/blog/resources/llm-copilot-ssh-remote.svg" /&gt;&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;To set this up on a Windows system, &lt;a href="https://www.openssh.org/"&gt;OpenSSH&lt;/a&gt; is required. OpenSSH must be installed on both the client VM and the target VM.&lt;/p&gt;
&lt;p&gt;The client VM additionally needs &lt;a href="https://services.qftest.com/en/download/"&gt;QF-Test (10.0 or newer)&lt;/a&gt; installed. The target VM must have a current &lt;a href="https://github.com/features/copilot/cli/"&gt;GitHub Copilot CLI&lt;/a&gt; installation, and the GitHub login (&lt;code&gt;/login&lt;/code&gt;) must have been completed within the SSH environment.&lt;/p&gt;
&lt;p&gt;To use GitHub Copilot CLI via remote SSH reliably, an &lt;strong&gt;SSH key is required&lt;/strong&gt;, because OpenSSH cannot handle SSH password prompts during automated remote calls (such as those triggered from QF-Test). SSH key authentication is needed solely to establish a passwordless, stable SSH connection.&lt;/p&gt;
&lt;h2&gt;Step-by-Step Guide&lt;/h2&gt;
&lt;h3&gt;Preparation on the Client VM&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Create the &lt;code&gt;.ssh&lt;/code&gt; directory&lt;/strong&gt; (if it doesn&amp;#8217;t exist, e.g. &lt;code&gt;C:\Users\Administrator\.ssh&lt;/code&gt;)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;New-Item&lt;/span&gt; &lt;span class="n"&gt;-ItemType&lt;/span&gt; &lt;span class="n"&gt;Directory&lt;/span&gt; &lt;span class="n"&gt;-Path&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;$env:USERPROFILE\.ssh&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;-Force&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Out-Null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Generate an SSH key (ed25519) on the client VM (without a passphrase)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ssh-keygen&lt;/span&gt; &lt;span class="n"&gt;-t&lt;/span&gt; &lt;span class="n"&gt;ed25519&lt;/span&gt; &lt;span class="n"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;qftest-remote&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;$env:USERPROFILE\.ssh\id_ed25519&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Simply leave the passphrase empty (press Enter twice).&lt;/li&gt;
&lt;li&gt;The generated public key (&lt;code&gt;…/.ssh/id_ed25519.pub&lt;/code&gt;) will be needed on the target VM later.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Preparation on the Target VM&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Set Access Control Lists (ACLs) correctly&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Windows OpenSSH only accepts keys if the file is in the right location &lt;strong&gt;and&lt;/strong&gt; the permissions are set with the correct ACLs. The file &lt;code&gt;C:\ProgramData\ssh\administrators_authorized_keys&lt;/code&gt; is created and secured as follows:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;icacls&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:\&lt;/span&gt;&lt;span class="n"&gt;ProgramData&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;administrators_authorized_keys&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;inheritance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;r&lt;/span&gt;
&lt;span class="n"&gt;icacls&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:\&lt;/span&gt;&lt;span class="n"&gt;ProgramData&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;administrators_authorized_keys&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;grant&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Administrators:F&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;icacls&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:\&lt;/span&gt;&lt;span class="n"&gt;ProgramData&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;administrators_authorized_keys&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;grant&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SYSTEM:F&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;Restart-Service&lt;/span&gt; &lt;span class="n"&gt;sshd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Add the public key&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Paste the contents of &lt;code&gt;id_ed25519.pub&lt;/code&gt; from the client VM into the &lt;code&gt;administrators_authorized_keys&lt;/code&gt; file on the target VM and save it (one key per line).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Log in to Copilot CLI in the SSH environment on the target VM (once)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Copilot stores its login credentials locally in the respective user profile. The login &lt;strong&gt;must&lt;/strong&gt; be completed beforehand — either directly on the target VM or via SSH, depending on whether different Windows users are involved.&lt;/p&gt;
&lt;p&gt;Once the Copilot login is saved in the SSH environment, Copilot can be fully scripted via remote SSH from QF-Test.&lt;/p&gt;
&lt;p&gt;Run the following in PowerShell if all client VMs should interact with the same user and SSH environment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SSH directly to the target VM: &lt;code&gt;ssh -i ~/.ssh/id_ed25519 Administrator@&amp;lt;target-IP&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start Copilot: &lt;code&gt;copilot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start login: &lt;code&gt;/login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Confirm the device code in the browser.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Functional Test (Client VM → Target VM, headless)&lt;/h3&gt;
&lt;p&gt;Run the following command on the client VM in PowerShell:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ssh&lt;/span&gt; &lt;span class="n"&gt;-i&lt;/span&gt; &lt;span class="p"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_ed25519&lt;/span&gt; &lt;span class="n"&gt;Administrator&lt;/span&gt;&lt;span class="p"&gt;@&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;target-IP&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;copilot -p &amp;#39;Say hello&amp;#39; --stream on&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Troubleshooting&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Test SSH key login from the client VM&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Verify that SSH works without a password using the SSH key (prerequisite for QF-Test):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ssh&lt;/span&gt; &lt;span class="n"&gt;-i&lt;/span&gt; &lt;span class="nv"&gt;$env:USERPROFILE&lt;/span&gt;&lt;span class="p"&gt;\.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="n"&gt;id_ed25519&lt;/span&gt; &lt;span class="n"&gt;Administrator&lt;/span&gt;&lt;span class="p"&gt;@&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;target-IP&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;echo OK&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;OK&lt;/code&gt; appears → key authentication is working.&lt;/li&gt;
&lt;li&gt;If there is an error: check the SSH keys or ACLs on the target VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Connect Copilot CLI to QF-Test via SSH&lt;/h3&gt;
&lt;p&gt;Once everything works, follow these instructions from step 2:&lt;/p&gt;
&lt;div class="anchor-wrapper"&gt;
    &lt;a href="https://www.qftest.com/en/blog/article/llm-copilot.html" class="container card scroll-animated width-normal clear imgfill   image-right"&gt;&lt;img class="container-image" src="/blog/resources/github-copilot-cli.webp" alt=""&gt;&lt;div class="container-content"&gt;&lt;div class="container-title"&gt;&lt;div class="container-title-text"&gt;Tutorial: Using GitHub Copilot with the QF-Test AI Integration&lt;/div&gt;&lt;/div&gt;

&lt;div class="entry-content"&gt;  &lt;p&gt;Since QF-Test 10, AI-driven capabilities are integrated directly into your testing workflow. This post shows how to connect &lt;strong&gt;GitHub Copilot&lt;/strong&gt; to QF-Test using a simple custom model and the &lt;strong&gt;Copilot CLI&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="container width-normal align-start justify-center row    " &gt;&lt;div class="container-content" &gt;&lt;address class="vcard author"&gt;
&lt;strong class="flex-row-dynamic gap-small"&gt;
                &lt;img src="/images/staff/max.jpg" height="60" width="60" class="author-profile-pic" alt="Max Melzer"&gt;
            Max Melzer
            &lt;/strong&gt;&lt;/address&gt;
&lt;div class="flex-column"&gt;&lt;div&gt;&lt;time class="published" datetime="2026-02-09T00:00:00+01:00"&gt;
        09/02/2026
&lt;/time&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;    &lt;/div&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Make the following adjustments to the &lt;code&gt;ai.addCustomModel&lt;/code&gt; script (insert the correct user, path and IP for your setup):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addCustomModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GitHub Copilot&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remoteUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Administrator&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remoteHost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;192.168.0.2&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// IP of target VM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remoteCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cmd /c \&amp;quot;C:/Users/Administrator/AppData/Local/GitHubCopilotCLI/copilot.exe --silent --stream off --model claude-sonnet-4.6 --prompt \\\&amp;quot;${msg}\\\&amp;quot;\&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ssh&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-o&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;StrictHostKeyChecking=no&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${remoteUser}@${remoteHost}&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;remoteCmd&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitFor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;● &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;Learn more about the AI integrations in QF-Test 10:&lt;/p&gt;
&lt;div class="anchor-wrapper"&gt;
    &lt;a href="https://www.qftest.com/en/solutions/test-automation-basics/ai-testautomation.html" class="container card scroll-animated width-normal   image-left  "&gt;&lt;img class="container-image" src="/images/qftest-ai.png" alt=""&gt;&lt;div class="container-content"&gt;&lt;div class="container-title"&gt;&lt;div class="container-title-text"&gt;AI-driven test automation with QF-Test&lt;/div&gt;&lt;/div&gt;&lt;div class="element-text"&gt;AI-driven testing and intelligent test automation for efficient software quality assurance&lt;/div&gt;    &lt;/div&gt;&lt;/a&gt;&lt;/div&gt;</content><category term="blog"/><category term="how-to"/><category term="ai"/></entry><entry><title>How to use the QF-Test AI integration with a free local model using Ollama</title><link href="https://www.qftest.com/en/blog/article/llm-local-ollama.html" rel="alternate"/><published>2026-03-09T00:00:00+01:00</published><updated>2026-03-09T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2026-03-09:/en/blog/article/llm-local-ollama.html</id><summary type="html">&lt;p&gt;QF-Test has always been about reliable, maintainable test automation for web, desktop, Java-based, and mobile applications. With the new &lt;strong&gt;AI integration&lt;/strong&gt; in QF-Test 10, you can enhance your tests with AI-driven insights — and the best part is, you don’t need to rely on cloud services. In this post, we …&lt;/p&gt;</summary><content type="html">&lt;p&gt;QF-Test has always been about reliable, maintainable test automation for web, desktop, Java-based, and mobile applications. With the new &lt;strong&gt;AI integration&lt;/strong&gt; in QF-Test 10, you can enhance your tests with AI-driven insights — and the best part is, you don’t need to rely on cloud services. In this post, we’ll show you how to connect QF-Test to a &lt;strong&gt;locally running, free AI model&lt;/strong&gt; using &lt;a href="https://ollama.com/"&gt;Ollama&lt;/a&gt;, so you can start experimenting with AI-enhanced testing right on your own machine.&lt;/p&gt;
&lt;h2&gt;Step 1: Set up Ollama&lt;/h2&gt;
&lt;p&gt;To get started, you’ll need to install &lt;strong&gt;Ollama&lt;/strong&gt; on the machine where your tests run. Ollama provides free, locally hosted AI models that powerful enough for the more basic parts of QF-Test’s AI integration.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download and install Ollama from the official website: &lt;a href="https://ollama.com/"&gt;https://ollama.com/&lt;/a&gt; (You don&amp;#8217;t need any account).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start the Ollama server by opening a terminal and running:  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
ollama serve&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In another terminal, download a model, which typically is a couple of gigabytes:  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
ollama pull llama3.2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By default, Ollama will host the model at &lt;code&gt;http://localhost:11434&lt;/code&gt; as long as &lt;code&gt;ollama serve&lt;/code&gt; is running. You can now use this endpoint in QF-Test to send AI requests locally — no internet connection required.&lt;/p&gt;
&lt;h2&gt;Step 2: Configure QF-Test to Use the Local AI&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Large Language Model integration options dialog in QF-Test" src="/blog/resources/qftest-ai-options-ollama.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can connect the Ollama server to QF-Test convieniently via the QF-Test &lt;strong&gt;Options dialog&lt;/strong&gt;, accessible via &lt;strong&gt;QF-Test → Options → AI Integration&lt;/strong&gt;. Click the &amp;#8220;plus&amp;#8221; icon to add a new row to the &amp;#8220;AI Configurations&amp;#8221; table and enter the model details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Type:&lt;/strong&gt; &amp;#8220;Ollama&amp;#8221;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name:&lt;/strong&gt; &amp;#8220;Local AI&amp;#8221; (or whatever you like) &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL:&lt;/strong&gt; &amp;#8220;http://localhost:11434&amp;#8221;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API key:&lt;/strong&gt; &amp;#8220;&amp;#8221; (leave blank)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model name:&lt;/strong&gt; &amp;#8220;llama3.2&amp;#8221; (the name of a model you added to Ollama via &lt;code&gt;ollama pull&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;QF-Test also makes it easy to integrate an AI model via the &lt;code&gt;ai&lt;/code&gt; scripting module, if you prefer the scripting approach.&lt;/p&gt;
&lt;p&gt;Here’s a minimal example of a &lt;a href="https://www.qftest.com/doc/manual/en/control.html#step_ServerScriptStep"&gt;server script&lt;/a&gt; that connects QF-Test to your locally running Ollama model:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Configure the AI model&lt;/span&gt;
&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefaultConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Ollama&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://localhost:11434&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;llama3.2&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;displayName:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Local AI&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Make a request to the AI and print it to the terminal&lt;/span&gt;
&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What is the answer to life, the universe and everything?&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;configName:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Local AI&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let’s break down what’s happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ai.setDefaultConfig(...)&lt;/code&gt;&lt;/strong&gt;:&lt;br /&gt;
   This line registers the model with QF-Test. You specify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The type of endpoint (&amp;#8220;Provider&amp;#8221;) to use (&lt;code&gt;Ollama&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The local endpoint url (&lt;code&gt;http://localhost:11434&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An empty string for the API key (not needed for local models)&lt;/li&gt;
&lt;li&gt;The model name (&lt;code&gt;llama3.2&lt;/code&gt; in our case)&lt;/li&gt;
&lt;li&gt;A friendly display name (&lt;code&gt;Local AI&lt;/code&gt;) for use in the UI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ai.ask(...)&lt;/code&gt;&lt;/strong&gt;:&lt;br /&gt;
 This line sends a prompt to the AI model. The &lt;code&gt;configName&lt;/code&gt; ensures it uses the local configuration we just defined. The result is returned directly and printed in the QF-Test terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 3: Use AI in your tests&lt;/h2&gt;
&lt;p&gt;Beyond this toy example, you can now leverage your local AI model directly in your test cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check Text with AI&lt;/strong&gt;&lt;br /&gt;
  The AI integration can be used in a &lt;strong&gt;Check Text with AI&lt;/strong&gt; node. This is perfect for situations where you want QF-Test to validate text in your application UI dynamically. For example, you could check if a text matches a natural language description or even check if it&amp;#8217;s in the expected language.
  To learn more, see the documentation here: &lt;a href="https://www.qftest.com/doc/manual/en/checks.html#step_AICheckTextStep"&gt;Check Text with AI node&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI Scripting Module&lt;/strong&gt;&lt;br /&gt;
  For advanced automation, the &lt;code&gt;ai&lt;/code&gt; scripting module gives you full programmatic control over AI requests in QF-Test:
  &lt;a href="https://www.qftest.com/doc/manual/en/tech_scripting_ai.html"&gt;AI scripting module documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The advantages of running locally&lt;/h2&gt;
&lt;p&gt;Running a local AI model has several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Privacy:&lt;/strong&gt; Your test data never leaves your machine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Local inference can be faster than cloud requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost-free:&lt;/strong&gt; No API subscription needed for experimentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By combining QF-Test’s robust automation framework with AI insights, you can enhance your tests, automate language checks, or even generate test suggestions — all without leaving your enterprise environment.&lt;/p&gt;
&lt;h2&gt;What&amp;#8217;s next&lt;/h2&gt;
&lt;p&gt;Once you have your local Ollama model running and integrated with QF-Test, try experimenting with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Natural language validations in your test cases&lt;/li&gt;
&lt;li&gt;Dynamic data generation for forms&lt;/li&gt;
&lt;li&gt;Multilingual text checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details on all AI integration options in QF-Test, check out our free special webinar &lt;strong&gt;&amp;#8220;When tests become intelligent: AI-driven checks with QF-Test&amp;#8221;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;[[qf.Preview(videos/2026-03-02-special-webinar-ai-checks)]]&lt;/p&gt;
&lt;p&gt;With just a few steps, your tests can now become &lt;strong&gt;AI-aware&lt;/strong&gt; while staying fully under your control. It’s time to explore the power of combining QF-Test with local AI models!&lt;/p&gt;</content><category term="blog"/><category term="how-to"/><category term="ai"/></entry><entry><title>Tutorial: Using GitHub Copilot with the QF-Test AI Integration</title><link href="https://www.qftest.com/en/blog/article/llm-copilot.html" rel="alternate"/><published>2026-02-09T00:00:00+01:00</published><updated>2026-02-09T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2026-02-09:/en/blog/article/llm-copilot.html</id><summary type="html">&lt;p&gt;Since QF-Test 10, AI-driven capabilities are integrated directly into your testing workflow. This post shows how to connect &lt;strong&gt;GitHub Copilot&lt;/strong&gt; to QF-Test using a simple custom model and the &lt;strong&gt;Copilot CLI&lt;/strong&gt;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;QF-Test is a comprehensive test automation solution built for Web, Desktop, Java-based, and Mobile applications, with a focus on long-term maintainability and enterprise-ready reliability. With the launch of &lt;strong&gt;QF-Test 10&lt;/strong&gt;, AI-driven capabilities are now integrated directly into your testing workflow. This post shows how to connect &lt;strong&gt;GitHub Copilot&lt;/strong&gt; to QF-Test using a simple custom model and the &lt;strong&gt;Copilot CLI&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Usually in QF-Test, AI providers are set up via &lt;a href="https://www.qftest.com/doc/manual/en/opt_llmintegration.html#opt_LLMProviders"&gt;&lt;em&gt;Options → Artificial Intelligence → AI Configurations&lt;/em&gt;&lt;/a&gt;. GitHub Copilot is an exception because it doesn&amp;#8217;t expose a standard API endpoint with a URL. Therefore, you cannot add Copilot in &lt;em&gt;Options → Artificial Intelligence → AI Configurations&lt;/em&gt;. The good news: you can still integrate Copilot by registering your own custom model in QF-Test that calls the GitHub Copilot CLI.&lt;/p&gt;
&lt;h2&gt;What you&amp;#8217;ll need&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://services.qftest.com/en/download/"&gt;QF-Test 10 or later&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Access to GitHub Copilot and GitHub Copilot CLI. As of now, a free GitHub account on the &amp;#8220;Free&amp;#8221; Copilot plan is enough to experiment with the Copilot CLI.&lt;/li&gt;
&lt;li&gt;Basic familiarity with scripting in QF-Test.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 1: Install and configure the GitHub Copilot CLI&lt;/h2&gt;
&lt;p&gt;&lt;img alt="" src="/blog/resources/github-copilot-cli.webp" /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the GitHub Copilot CLI&lt;br /&gt;
&lt;a href="https://docs.github.com/en/copilot/how-tos/copilot-cli"&gt;Follow the official instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Log in from your Terminal&lt;br /&gt;
    Run &lt;code&gt;copilot login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Verify it works&lt;br /&gt;
    Try a quick prompt: &lt;code&gt;copilot --prompt "Hello World"&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If that prints a response from Copilot, you&amp;#8217;re ready to connect it to QF-Test.&lt;/p&gt;
&lt;h2&gt;Step 2: Wire Copilot into QF-Test via a custom model&lt;/h2&gt;
&lt;p&gt;We&amp;#8217;ll use &lt;a href="https://www.qftest.com/doc/manual/en/tech_scripting_ai.html#ai_method_addCustomModel"&gt;&lt;code&gt;ai.addCustomModel&lt;/code&gt;&lt;/a&gt; in a &lt;a href="https://www.qftest.com/doc/manual/en/control.html#step_ServerScriptStep"&gt;Server Script&lt;/a&gt; so QF-Test can talk to Copilot by launching the CLI as a subprocess. In QF-Test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new Server Script node (language: Groovy).&lt;/li&gt;
&lt;li&gt;Paste the code below into the script.&lt;/li&gt;
&lt;li&gt;Adjust the path to the &lt;code&gt;copilot&lt;/code&gt; executable. &lt;code&gt;/opt/homebrew/bin/copilot&lt;/code&gt; will only work if Copilot CLI was installed via Homebrew on macOS.&lt;/li&gt;
&lt;li&gt;Run the script node.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addCustomModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GitHub Copilot&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/opt/homebrew/bin/copilot&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--silent&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--stream&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;off&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--model&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;claude-sonnet-4.5&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--prompt&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitFor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;● &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;What is the answer to life, the universe and everything?&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;configName:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GitHub Copilot&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What this does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Registers a LLM model named &amp;#8220;GitHub Copilot&amp;#8221; with the QF-Test AI integration.&lt;/li&gt;
&lt;li&gt;For each prompt (&lt;code&gt;msg&lt;/code&gt;), QF-Test starts the Copilot CLI and returns the CLI&amp;#8217;s text output back to QF-Test.&lt;/li&gt;
&lt;li&gt;Prints the result of a sample &lt;code&gt;ai.ask&lt;/code&gt; call in the QF-Test terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Tip: You can change the model name passed to &lt;code&gt;--model&lt;/code&gt; to your preferred Copilot model.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you get an error saying &lt;code&gt;Cannot run program "/path/to/copilot": error=2, No such file or directory&lt;/code&gt;, you must update the path in the first item in the &lt;code&gt;cmd&lt;/code&gt; list to where the Copilot CLI is installed on your system.&lt;/p&gt;
&lt;p&gt;If everything works, you can remove the &lt;code&gt;println&lt;/code&gt; line at the end.&lt;/p&gt;
&lt;h2&gt;Step 3: Use Copilot with the QF-Test &lt;code&gt;ai&lt;/code&gt; scripting module&lt;/h2&gt;
&lt;p&gt;Once the custom model is registered, you can use it from any script with &lt;code&gt;ai.ask&lt;/code&gt;, for example:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generate a JSON string of test data in the following format: ...&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;configName:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GitHub Copilot&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLocal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For more on the &lt;code&gt;ai&lt;/code&gt; scripting module in QF-Test, see the &lt;a href="https://www.qftest.com/doc/manual/en/tech_scripting_ai.html"&gt;QF-Test manual&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Step 4: Validate UI text with &amp;#8220;Check text with AI&amp;#8221;&lt;/h2&gt;
&lt;p&gt;You can also bring Copilot into your test checks. The &lt;a href="https://www.qftest.com/doc/manual/en/checks.html#step_AICheckTextStep"&gt;&amp;#8220;Check text with AI&amp;#8221;&lt;/a&gt; node lets you  have the AI verify UI elements in your application using natural language. In the node&amp;#8217;s &amp;#8220;AI configuration&amp;#8221; field, &amp;#8220;GitHub Copilot&amp;#8221; should automatically appear in the dropdown.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The &amp;quot;Check text with AI&amp;quot; node in QF-Test" src="https://www.qftest.com/doc/manual/en/images/att_AICheckTextStep.png" /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That&amp;#8217;s it! Even though Copilot doesn&amp;#8217;t expose a standard URL+API key endpoint, you can still use it inside QF-Test by registering a custom model that calls the Copilot CLI. From there, &lt;code&gt;ai.ask&lt;/code&gt; helps you validate and generate data right in your tests, and &amp;#8220;Check text with AI&amp;#8221; lets you validate UI content in plain language. Happy testing!&lt;/p&gt;
&lt;p&gt;For more details on all AI integration options in QF-Test, see our free special webinar &lt;strong&gt;&amp;#8220;When tests become intelligent: AI-driven checks with QF-Test&amp;#8221;&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="video-card-horizontal flex-row-dynamic flex-wrap"&gt;
    &lt;a  href="https://www.qftest.com/en/support/videos/special-webinar-ai-checks.html" rel="bookmark" title="When tests become intelligent: AI-driven checks with QF-Test"&gt;
        &lt;img src="/videos/resources/special-webinar-qftest-ai-en.jpg" class="scroll-animated" width="640" height="480" alt=""&gt;
    &lt;/a&gt;
    &lt;div class="entry-content"&gt;
      &lt;h3 class="entry-title"&gt;
        &lt;a class="title" href="https://www.qftest.com/en/support/videos/special-webinar-ai-checks.html" rel="bookmark" title="When tests become intelligent: AI-driven checks with QF-Test"&gt;
            When tests become intelligent: AI-driven checks with QF-Test
        &lt;/a&gt;
      &lt;/h3&gt;
  &lt;p&gt;In this special webinar, we&amp;#8217;ll show you how to get the most out of the new AI integrations in QF-Test. With QF-Test 10, you can use AI to test non-deterministic UIs, validate UI components based on semantic criteria, generate test data, and much more.&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class="anchor-wrapper"&gt;
    &lt;a href="https://www.qftest.com/en/blog/article/llm-copilot-ssh-remote.html" class="container card scroll-animated width-normal clear imgfill   image-right"&gt;&lt;img class="container-image" src="/blog/resources/llm-copilot-ssh-remote.svg" alt=""&gt;&lt;div class="container-content"&gt;&lt;div class="container-title"&gt;&lt;div class="container-title-text"&gt;Using GitHub Copilot CLI via Remote SSH from QF-Test&lt;/div&gt;&lt;/div&gt;

&lt;div class="entry-content"&gt;  &lt;p&gt;This quick guide explains how to use the &lt;strong&gt;GitHub Copilot CLI&lt;/strong&gt; via a remote SSH connection directly from &lt;strong&gt;QF-Test&lt;/strong&gt; — including SSH key setup and troubleshooting.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="container width-normal align-start justify-center row    " &gt;&lt;div class="container-content" &gt;&lt;address class="vcard author"&gt;
Irfan Yigit, ASC Technologies&lt;/address&gt;
&lt;div class="flex-column"&gt;&lt;div&gt;&lt;time class="published" datetime="2026-04-01T00:00:00+02:00"&gt;
        01/04/2026
&lt;/time&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;    &lt;/div&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="/en/solutions/test-automation-basics/ai-testautomation.html"&gt;More about AI-driven testautomation&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/><category term="ai"/></entry><entry><title>Testing ZK Applications with QF-Test in 2026</title><link href="https://www.qftest.com/en/blog/article/testing-zk-applications-2026.html" rel="alternate"/><published>2026-02-05T00:00:00+01:00</published><updated>2026-02-05T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2026-02-05:/en/blog/article/testing-zk-applications-2026.html</id><summary type="html">&lt;p&gt;&lt;em&gt;This text was &lt;a href="https://docs.zkoss.org/small-talk/2026/02/05/qftest-testing-zk-applications"&gt;originally published as a ZK Small Talk&lt;/a&gt;. &lt;/em&gt;&lt;em&gt;ZK&lt;/em&gt;&lt;em&gt; is a framework for building web applications with Java and a long-standing partner of QF-Test.&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;This text was &lt;a href="https://docs.zkoss.org/small-talk/2026/02/05/qftest-testing-zk-applications"&gt;originally published as a ZK Small Talk&lt;/a&gt;. &lt;/em&gt;&lt;em&gt;ZK&lt;/em&gt;&lt;em&gt; is a framework for building web applications with Java and a long-standing partner of QF-Test.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img alt="" src="/blog/resources/qftest-zk-3d-compare.png" /&gt;&lt;/p&gt;
&lt;p&gt;The great thing about ZK has always been that you can build web application without having to deal with the hassle of writing and maintaining HTML and JavaScript code. You define your application UI in concise &lt;a href="https://docs.zkoss.org/zk_dev_ref/ui_composing/zuml"&gt;&lt;code&gt;ZUML&lt;/code&gt; markup&lt;/a&gt; and little &lt;a href="https://docs.zkoss.org/zk_dev_ref/ui_composing/richlet"&gt;richlets&lt;/a&gt; written in Java, you can write unit tests for your business logic and even use &lt;a href="https://www.zkoss.org/product/zats"&gt;ZATS&lt;/a&gt; to test your UI.&lt;/p&gt;
&lt;div class="flex-row justify-center my width-normal button-wrapper"&gt;
    &lt;a href="https://docs.zkoss.org/small-talk/2026/02/05/qftest-testing-zk-applications"  class="button scroll-animated green chevron"&gt;Read more on the ZK Small Talks blog&lt;svg xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32"&gt;
    &lt;path d="M13.697 22.317l6.85-6.658L13.696 9 12 10.65l5.152 5.009L12 20.667z"&gt;
    &lt;/path&gt;
&lt;/svg&gt;&lt;/a&gt;
&lt;/div&gt;</content><category term="blog"/><category term="how-to"/><category term="testing"/></entry><entry><title>QF-Test Featured in mgm technology partners’ Blog on Quality and Efficiency Strategy</title><link href="https://www.qftest.com/en/blog/article/quality-engineering.html" rel="alternate"/><published>2026-01-29T00:00:00+01:00</published><updated>2026-01-29T00:00:00+01:00</updated><author><name>Monika Dühring</name></author><id>tag:www.qftest.com,2026-01-29:/en/blog/article/quality-engineering.html</id><summary type="html">&lt;p&gt;In their latest blog post, mgm technology partners presents their holistic approach to &lt;strong&gt;Quality Engineering&lt;/strong&gt;. Here we explain why we also find this approach worthwhile and how QF-Test fits into it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the latest post on the mgm technology partners blog, &lt;a href="https://insights.mgm-tp.com/en/2026/quality-assurance/how-quality-engineering-makes-complex-systems-manageable/"&gt;&amp;#8220;How Quality Engineering Makes Complex Systems Manageable&amp;#8221;&lt;/a&gt;, &lt;a href="/author/lilia-gargouri.html"&gt;Lilia Gargouri&lt;/a&gt; presents their holistic approach to quality engineering &amp;ndash; with a focus on sustainable, scalable quality assurance in complex software projects. Unlike traditional testing, this approach emphasizes a systematic interplay of architecture, processes, data, models, and tools, making quality throughout the entire software lifecycle both predictable and controllable.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://insights.mgm-tp.com/wp-content/uploads/2025/08/image-2025-08-19-09-23-05-358.jpg" /&gt;&lt;/p&gt;
&lt;h2&gt;A particularly interesting aspect&lt;/h2&gt;
&lt;p&gt;The article highlights the critical role automation plays in this approach—and this is where QF-Test comes in. As a powerful tool for automated end-to-end and &lt;a href="/en/solutions/types-of-tests.html"&gt;UI testing&lt;/a&gt;, QF-Test delivers exactly the scalability and repeatability that complex applications require, helping teams use resources efficiently while ensuring high quality.&lt;/p&gt;
&lt;h2&gt;Why QF-Test matters for quality engineering&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Automated tests that go beyond classic manual methods to &lt;strong&gt;ensure consistent, reproducible quality&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support for different types of applications&lt;/strong&gt; (web, desktop, Android, iOS applications, etc.), making it easier to integrate into diverse projects.&lt;/li&gt;
&lt;li&gt;Contribution to a shift-left strategy, helping to prevent defects early in the development process and thereby &lt;strong&gt;reducing effort and cost&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Overall, the article demonstrates how test automation—and especially the use of QF-Test as part of a test and quality engineering strategy—helps manage the challenges of complex software environments and ensures the highest quality standards.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://insights.mgm-tp.com/en/2026/quality-assurance/how-quality-engineering-makes-complex-systems-manageable/"&gt;How Quality Engineering Makes Complex Systems Manageable&lt;/a&gt; by Lilia Gargouri, Head of Quality Engineering at mgm technology partners&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>Scrolling on the Web with QF-Test – The complete guide</title><link href="https://www.qftest.com/en/blog/article/web-scrolling.html" rel="alternate"/><published>2025-07-16T00:00:00+02:00</published><updated>2025-07-16T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2025-07-16:/en/blog/article/web-scrolling.html</id><summary type="html">&lt;p&gt;In this blog post, I will tell you all about how to control how QF-Test scrolls your web application and how to control that scrolling yourself.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A question that comes up time and again during web application test automation with QF-Test is: &lt;strong&gt;How do I scroll?&lt;/strong&gt; How do I trigger a scroll event? Can I tell QF-Test to turn the mouse wheel? Can I simulate scrolling with the mouse in QF-Test? I just want to scroll down a little!&lt;/p&gt;
&lt;p&gt;In general, our first response to these kinds of questions is: &lt;strong&gt;You shouldn&amp;#8217;t have to.&lt;/strong&gt; QF-Test handles scrolling by itself and will fully automatically scroll elements into the viewport as soon as it needs to interact with them. That&amp;#8217;s why QF-Test does not record scroll events, or offer a &amp;#8216;Scroll&amp;#8217; node, because in 99% of cases, QF-Test will do the right thing automatically.&lt;/p&gt;
&lt;p&gt;But you&amp;#8217;re probably here because you&amp;#8217;re part of that 1% of cases where that mechanism is not enough. So in this blog post, I will tell you all about how QF-Test scrolls your web application and how to control that scrolling yourself.&lt;/p&gt;
&lt;h2&gt;Scroll manually using keyboard events&lt;/h2&gt;
&lt;p&gt;If you just want to scroll a set amount, you should try to scroll by recording keyboard events.&lt;/p&gt;
&lt;p&gt;In most web applications, the arrow keys can be used to scroll around. &lt;kbd&gt;Space&lt;/kbd&gt; and &lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;Space&lt;/kbd&gt; will scroll roughly one window height, and the &lt;kbd&gt;Home&lt;/kbd&gt; and &lt;kbd&gt;End&lt;/kbd&gt; keys should scroll to the top and bottom of the document, respectively. That way you might be able to skip the more complicated methods described below. &lt;/p&gt;
&lt;h2&gt;Fine-tune the QF-Test scrolling algorithm&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s say QF-Test is already scrolling for you, but it&amp;#8217;s not quite doing it the way you need. QF-Test offers several SUT options you can use to configure its scrolling behavior.&lt;/p&gt;
&lt;p&gt;You can set these options in a &lt;a href="https://www.qftest.com/doc/manual/en/user_scripting.html#usec_scripting_options"&gt;SUT script node&lt;/a&gt; or using the &lt;a href="https://www.qftest.com/doc/manual/en/tech_doctags.html#sec_doctags_execution"&gt;&lt;code&gt;@option&lt;/code&gt; doctag&lt;/a&gt; in QF-Test 9.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Options.OPT_WEB_SCROLL_VISIBLE&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This option controls how web nodes are scrolled visible. Possible values are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Options.VAL_WEB_SCROLL_VISIBLE_DEFAULT&lt;/code&gt; (use the QF-Test algorithm),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Options.VAL_WEB_SCROLL_VISIBLE_NEVER&lt;/code&gt; (prohibits scrolling),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Options.VAL_WEB_SCROLL_VISIBLE_TOP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Options.VAL_WEB_SCROLL_VISIBLE_BOTTOM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Options.VAL_WEB_SCROLL_VISIBLE_MINIMAL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a custom JSON string matching the &lt;code&gt;options&lt;/code&gt; parameter of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView"&gt;&lt;code&gt;Element:scrollIntoView&lt;/code&gt; JavaScript API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Options other than &amp;#8220;default&amp;#8221; and &amp;#8220;never&amp;#8221; will call &lt;code&gt;node.scrollIntoView()&lt;/code&gt; directly in the browser&amp;#8217;s JavaScript context. &lt;code&gt;scrollIntoView&lt;/code&gt; is a pretty powerful part of the JavaScript Element API. You can learn all about it&amp;#8217;s advanced options in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView"&gt;mdn web docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also override this option on an individual component by setting the node property &lt;code&gt;qfs:scrollvisible&lt;/code&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getComponent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;myElement&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// replace with a component&amp;#39;s QF-Test ID&lt;/span&gt;
&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;qfs:scrollvisible&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VAL_WEB_SCROLL_VISIBLE_NEVER&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;&lt;code&gt;Options.OPT_WEB_MIN_DELAY_AFTER_AUTO_SCROLL&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;With this option, you can introduce some additional delay after every automatic scroll action, in &lt;code&gt;ms&lt;/code&gt;. This is useful if your application uses animations that are triggered by scrolling or if your application just needs a little more time to reload data after scrolling. The default value is 150.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Options.OPT_PLAY_SCROLL_ITEM&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Set to &lt;code&gt;False&lt;/code&gt; to stop QF-Test from automatically scrolling to sub-items of lists, tables or trees. You can use this if QF-Test scrolls to weird places when navigating a tree, for example.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Options.OPT_PLAY_SCROLL_PADDING&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Extra padding for automatic scrolling, in &lt;code&gt;px&lt;/code&gt;. Use this to make QF-Test increase or decrease the calculated minimal required scroll amount so that more of the surroundings of the target element remain visible. The default value is 5.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Options.OPT_PLAY_WEB_AUTO_SCROLL&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When set to &lt;code&gt;False&lt;/code&gt;, QF-Test will not perform any auto-scrolling at all. Use this to completely opt-out of automatic scrolling and handle it manually.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Scroll components into view&lt;/h2&gt;
&lt;p&gt;You can trigger the QF-Test scrolling algorithm manually in an SUT script like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getComponent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;myElement&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// replace with a component&amp;#39;s QF-Test ID&lt;/span&gt;
&lt;span class="c1"&gt;// Boolean scrollPointVisible(int targetX, int targetY, boolean propagate)&lt;/span&gt;
&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;scrollPointVisible&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will try to scroll the element so that it becomes visible. If the element cannot be made completely visible, it will scroll it so that at least the requested target point becomes visible.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If the QF-Test algorithm is not helpful in your situation, maybe because you want to trigger a scroll action by itself, you can use a small SUT script which calls any JavaScript scrolling function like this: &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getComponent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;myElement&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// replace with a component&amp;#39;s QF-Test ID&lt;/span&gt;
&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;evalJS&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_qf_node.scrollIntoView()&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// arbitrary JavaScript&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Scroll by set amounts&lt;/h2&gt;
&lt;p&gt;But what if you need even more control over the scrolling or don&amp;#8217;t have a particular component you want to scroll to?&lt;/p&gt;
&lt;p&gt;You can also any scroll container by a precise amount using JavaScript.&lt;/p&gt;
&lt;p&gt;For example, this SUT script would scroll the window down by 25 pixels:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getComponent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;genericDocument&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;evalJS&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;window.scrollBy(0,25)&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first parameter of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollBy"&gt;the &lt;code&gt;scrollBy&lt;/code&gt; method&lt;/a&gt; controls the x-axis, so you can even scroll sideways using this method.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;And that&amp;#8217;s it. That was all you need to know about scrolling web content with QF-Test to get you out of even the trickiest situations.&lt;/p&gt;
&lt;p&gt;If you are still stuck, you can &lt;a href="https://services.qftest.com/en/support/request/"&gt;get in touch with our support team&lt;/a&gt;, who are always happy to help!&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Maximizing SUT windows – a good idea?</title><link href="https://www.qftest.com/en/blog/article/maximizing-windows.html" rel="alternate"/><published>2025-06-03T00:00:00+02:00</published><updated>2025-06-03T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2025-06-03:/en/blog/article/maximizing-windows.html</id><summary type="html">&lt;p&gt;Scale windows in QF-Test rather than maximize them &amp;ndash; this ensures stable and reproducible tests. If you still need full-screen mode, you can maximize windows with a small script.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="" src="/blog/resources/maximize-window.png" /&gt;&lt;/p&gt;
&lt;p&gt;In QF-Test product support we are often asked how to set a QF-Test controlled browser or other SUT windows to fullscreen mode.&lt;/p&gt;
&lt;h2&gt;It&amp;#8217;s better to work with fixed window sizes&lt;/h2&gt;
&lt;p&gt;In fact, we advise against maximizing windows for the following reason: The maximized window size depends on the system, a fixed window size is the same on every system instead.&lt;/p&gt;
&lt;p&gt;QF-Test is comparatively resistant to changes in component coordinates. However, web applications in particular can display completely different page layouts depending on the window size. In the worst case, this can lead to individual components no longer being found.&lt;/p&gt;
&lt;p&gt;If the browser is always started with a fixed window size instead, the test behaves uniformly on all computers.&lt;/p&gt;
&lt;p&gt;In the preparation sequence that opens the browser, you will find an &amp;#8220;Open browser window&amp;#8221; node below the &amp;#8220;Start SUT if necessary&amp;#8221; node, in which you can specify a uniform window size.&lt;/p&gt;
&lt;h2&gt;How to use fullscreen anyway&lt;/h2&gt;
&lt;p&gt;If you &lt;em&gt;still&lt;/em&gt; want to maximize your browser window, you can use the following Jython server script in QF-Test to maximize all windows containing e.g. &amp;#8220;Edge&amp;#8221; in the window title under Windows:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;autowin&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autowin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getAllWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autowin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getWindowText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.*Edge.*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;autowin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maximizeWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can customize the regular expression &lt;code&gt;".*Edge.*"&lt;/code&gt; to address exactly the window you are interested in with its window title.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>How can I create screenshots of my client application with QF-Test?</title><link href="https://www.qftest.com/en/blog/article/creating-screenshot-files.html" rel="alternate"/><published>2025-05-20T00:00:00+02:00</published><updated>2025-05-20T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2025-05-20:/en/blog/article/creating-screenshot-files.html</id><summary type="html">&lt;p&gt;Screenshots from QF-Test can be saved easily with a simple script. This allows screen recordings to be used specifically for further analysis or documentation purposes &amp;ndash; without any detour via the run log.&lt;/p&gt;</summary><content type="html">&lt;p&gt;QF-Test only provides on-board means to write screenshots of a client application to the test-run log. This can be done via the &amp;#8220;Message&amp;#8221; node or the procedure &lt;code&gt;qfs.run-log.screenshots.logScreenshot&lt;/code&gt; from the qfs.qft standard library.&lt;/p&gt;
&lt;h2&gt;Save screenshots to a file&lt;/h2&gt;
&lt;p&gt;Sometimes you don&amp;#8217;t want to store a screenshot in the test-run log but use it for other things and store it in a directory first. This is of course also possible with QF-Test, but it requires a script &amp;ndash; but just a very small one:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;imagewrapper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;
&lt;span class="n"&gt;screenshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grabImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#Window:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;screenshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&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;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qftest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/screenshot.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="" src="/blog/resources/screenshot-qftest-com-en.png" /&gt;&lt;/p&gt;
&lt;p&gt;This Jython SUT script uses the exact same mechanism to create screenshots that is used for the test run log.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rc.groups.qftest.suite.dir&lt;/code&gt; is a useful shortcut to get the directory of the current test suite. You can of course also call &lt;code&gt;writeToFile()&lt;/code&gt; with any other path. &lt;code&gt;"#Window:"&lt;/code&gt; is a &lt;a href="https://www.qftest.com/doc/manual/de/user_smartid.html"&gt;SmartID&lt;/a&gt; and stands for the first window of your application. You can also specify a more specific component here to take a screenshot of a specific part of the application UI.&lt;/p&gt;
&lt;p&gt;You can read more about the &lt;code&gt;ImageWrapper&lt;/code&gt; class in the QF-Test manual: &lt;a href="https://www.qftest.com/doc/manual/en/tech_scripting.html"&gt;The &lt;code&gt;ImageWrapper&lt;/code&gt; class&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>It’s not magic: How the CustomWebResolver makes your web application UI testable</title><link href="https://www.qftest.com/en/blog/article/cwr-testable-web-ui.html" rel="alternate"/><published>2025-04-04T00:00:00+02:00</published><updated>2025-04-04T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2025-04-04:/en/blog/article/cwr-testable-web-ui.html</id><summary type="html">&lt;p&gt;We explain why QF-Test is so much better at testing web applications than other tools and what this &amp;#8220;CustomWebResolver&amp;#8221; has to do with it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;With QF-Test, our goal is to make testing web application UIs as easy as testing a native application. This presents some challenges for which we developed a unique solution we call the &lt;strong&gt;CustomWebResolver&lt;/strong&gt;. In this blog post, I will explain why QF-Test is so much better at testing web applications than other tools and what this &amp;#8220;CustomWebResolver&amp;#8221; has to do with it.&lt;/p&gt;
&lt;h2&gt;The problem with CSS and XPath selectors&lt;/h2&gt;
&lt;p&gt;Most browser-based UI testing tools rely on CSS selectors or XPath to select an element to interact with or validate. This approach has several downsides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Very verbose:&lt;/strong&gt; A typical CSS selector looks like this: &lt;code&gt;body &amp;gt; main &amp;gt; div  &amp;gt; ul &amp;gt; li:nth-of-type(3) &amp;gt; button.my-action&lt;/code&gt;, the same as an XPath would be: &lt;code&gt;.//body/main/div/ul/li[3]/button[@class="my-action"]&lt;/code&gt;. Not very readable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not very stable:&lt;/strong&gt; These selectors rely on the underlying HTML structure of the application to remain the same, otherwise they break.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not easy to auto-generate:&lt;/strong&gt; This becomes important when recording an interaction. Generating a CSS or XPath selector for an HTML element programmatically is easy. Generating a &lt;em&gt;stable, concise and reusable&lt;/em&gt; selector, however, must usually be done by hand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Shadow DOM access:&lt;/strong&gt; It is not possible to use CSS or XPath to select an element encapsulated in a &amp;#8220;shadow root&amp;#8221;. This is a problem with applications built on the Web Components standard. Instead, JavaScript and a deep understanding of web technologies are required.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technology-specific:&lt;/strong&gt; These selectors are only applicable to HTML, they cannot be used in a cross-platform context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No checking of state:&lt;/strong&gt; With these selectors, you only &lt;em&gt;found&lt;/em&gt; the element. Checking if the element has the required state to pass a test needs to be handled separately.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;There is a better way: The QF-Test CustomWebResolver&lt;/h2&gt;
&lt;p&gt;Although you can also work with selectors here, QF-Test offers a better way: A technology we call the &lt;strong&gt;CustomWebResolver&lt;/strong&gt;, or &lt;strong&gt;CWR&lt;/strong&gt; for short, resolves the raw HTML of your application into a semantic tree of well-defined UI components. This offers several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No knowledge of HTML required:&lt;/strong&gt; Test authors are not required to understand the intricacies of the DOM structure of the application to address components.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic tests:&lt;/strong&gt; UI components can be addressed based on their semantic function as presented to the user, instead their structural position in the DOM or the HTML code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unified handling of similar components:&lt;/strong&gt;  UI components that have the same semantic function but are implemented differently on the HTML level are still addressed in the same way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checks included:&lt;/strong&gt; The state of a UI component can automatically be checked according to common criteria like &amp;#8220;is it visible&amp;#8221;, &amp;#8220;what&amp;#8217;s the text content?&amp;#8221;, &amp;#8220;what&amp;#8217;s the input value?&amp;#8221;, &amp;#8220;is it enabled or disabled?&amp;#8221;, &amp;#8220;is it checked or selected?&amp;#8221;, and more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;#8217;t worry about the Shadow DOM:&lt;/strong&gt; With a CustomWebResolver you never have to think about things like shadow roots – QF-Test just takes care of everything.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robust against DOM changes:&lt;/strong&gt; The CustomWebResolver is much more resilient against changes to the DOM structure. And if changes are necessary, they can be made in a central place instead of everywhere a component is used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-platform:&lt;/strong&gt; Because it is not bound to HTML, the QF-Test component structure can be reused between applications built with Java UI libraries, Native Windows UIs and even iOS and Android apps with little adjustment needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selection via SmartID:&lt;/strong&gt; In QF-Test you can select a component via the very compact &lt;a href="https://www.qftest.com/doc/manual/en/user_smartid.html"&gt;SmartID syntax&lt;/a&gt; like this: &lt;code&gt;#List:&amp;amp;2@#Button:&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Okay, but how does this magical CustomWebResolver technology work?&lt;/h2&gt;
&lt;p&gt;The basis of the CustomWebResolver is the translation of the DOM structure into a &lt;em&gt;tree of components&lt;/em&gt; of certain &lt;em&gt;generic classes&lt;/em&gt; based on a set of &lt;em&gt;mappings&lt;/em&gt;. Let&amp;#8217;s untangle that statement.&lt;/p&gt;
&lt;h3&gt;Generic classes&lt;/h3&gt;
&lt;p&gt;Any application UI consists of a number of widgets, or &lt;em&gt;components&lt;/em&gt;, that serve some purpose. There are usually windows with different panels which contain labels, tables, trees, lists, checkboxes, text inputs, sliders, drop-downs, and so on.&lt;/p&gt;
&lt;p&gt;QF-Test comes with a big list of &lt;a href="https://www.qftest.com/doc/manual/en/tech_genericclasses.html#sec_genericclasses"&gt;&lt;em&gt;generic classes&lt;/em&gt;&lt;/a&gt; which can be used to describe almost every component imaginable in an application UI. QF-Test understands their &lt;em&gt;semantics&lt;/em&gt;, i.e. how they work, and can automatically offer the right actions and checks per component.&lt;/p&gt;
&lt;p&gt;For example, QF-Test knows to offer a &amp;#8220;&amp;#8216;checked&amp;#8217; state&amp;#8221; check for the generic class &lt;code&gt;CheckBox&lt;/code&gt;, but not for e.g. the generic class &lt;code&gt;TextField&lt;/code&gt;, and it understands that you can click a &lt;code&gt;CheckBox&lt;/code&gt; component to toggle it&amp;#8217;s &amp;#8216;checked&amp;#8217; state. It also understands that a &lt;code&gt;Table&lt;/code&gt; or &lt;code&gt;List&lt;/code&gt; can contain child elements and gives you easy access to them.&lt;/p&gt;
&lt;h3&gt;CWR mappings&lt;/h3&gt;
&lt;p&gt;The job of the &lt;em&gt;CustomWebResolver&lt;/em&gt; is to look at the DOM of your application and figure out which HTML elements belong to which generic class. To do this, it uses &lt;em&gt;mappings&lt;/em&gt; which are usually defined in a YAML document. The simplest mapping possible looks like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;genericClasses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;btn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This piece of YAML, when interpreted by the CustomWebResolver, will tell QF-Test to assign every HTML element with the CSS class &lt;code&gt;btn&lt;/code&gt; the generic class &lt;code&gt;Button&lt;/code&gt; and therefore understand that it&amp;#8217;s a button that can be activated as long as it&amp;#8217;s not disabled or invisible.&lt;/p&gt;
&lt;p&gt;With the CustomWebResolver configured like this, you can now select &amp;#8220;OK&amp;#8221; buttons using the SmartID &lt;code&gt;#Button:OK&lt;/code&gt;, regardless of where this button is or what HTML tag it actually uses, as long as it has the CSS class &lt;code&gt;btn&lt;/code&gt; and a label &amp;#8220;OK&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Here is another example of a CustomWebResolver mapping, this time a more complex one:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;genericClasses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ComboBox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;role&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;attributeValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;combobox&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;List:ComboBoxList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ul&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ComboBox&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Item:ComboBoxListItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;li&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;List:ComboBoxList&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The mapping above is for a &lt;code&gt;ComboBox&lt;/code&gt;, i.e. some kind of drop-down select input. It actually consists of multiple sub-components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The drop-down itself,&lt;/li&gt;
&lt;li&gt;the list that appears when the drop-down is opened and&lt;/li&gt;
&lt;li&gt;the individual selectable options.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thanks to these mappings, the CustomWebResolver can tell QF-Test to handle this kind of drop-down as a single semantic component: QF-Test can automatically select a given option in the list, it can fetch the current selection or check that a given option is the current selection, or it can return the entire list of available options &amp;ndash; all through the QF-Test UI without writing any code or messing with the DOM.&lt;/p&gt;
&lt;h3&gt;The component tree&lt;/h3&gt;
&lt;p&gt;Finally, once all relevant components are mapped to generic classes, QF-Test can discard the rest of the HTML and you are left with a &lt;em&gt;beautifully simple tree of semantic components&lt;/em&gt;, on which you can build your tests.&lt;/p&gt;
&lt;p&gt;&lt;button class="plain" onclick="document.getElementById('2978').showModal()" title="Enlarge"&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;img src="/blog/resources/reduction-of-complexity-side-by-side.png"
        class=" scroll-animated"
        alt=""
        style=" "&gt;&lt;figcaption&gt;&lt;p&gt;A side-by-side illustration of how the complexity of the DOM tree of a web application is reduced if all the DIVs and SPANs are replaced with semantic components. The non-optimized tree on the left has way deeper nesting than the optimized, semantic tree.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;/button&gt;&lt;dialog popover id="2978" anchor="trigger-2978"&gt;&lt;/p&gt;
&lt;div class="container card scroll-animated width-full accent    "&gt;&lt;div class="container-content"&gt;        &lt;button onclick="document.getElementById('2978').close()"
                class="button blue tertiary"
                id="trigger-2978"
                style="margin: -15px -15px 10px auto; position: relative"&gt;Close&lt;/button&gt;
            &lt;figure&gt;
        &lt;img class="plain" src="/blog/resources/reduction-of-complexity-side-by-side.png" alt=""&gt;&lt;figcaption&gt;&lt;p&gt;A side-by-side illustration of how the complexity of the DOM tree of a web application is reduced if all the DIVs and SPANs are replaced with semantic components. The non-optimized tree on the left has way deeper nesting than the optimized, semantic tree.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
    &lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;/dialog&gt;&lt;/p&gt;
&lt;h2&gt;And how do I configure this CustomWebResolver for my application?&lt;/h2&gt;
&lt;p&gt;The only thing users of QF-Test have to do is to provide the initial configuration for the CustomWebResolver. We have implemented three ways to make this as easy as possible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For a growing list of web component frameworks, we bundle comprehensive CWR configurations with QF-Test. QF-Test will automatically recognize and translate any components from these frameworks into generic classes, ready for you to use in your tests and checks. We support popular frameworks like &lt;strong&gt;Vaadin&lt;/strong&gt;, &lt;strong&gt;Angular Material UI&lt;/strong&gt;, and &lt;strong&gt;Google Web Toolkit&lt;/strong&gt;. Check out &lt;a href="https://www.qftest.com/doc/manual/en/tech_ajax.html#sec_ajax"&gt;the full list&lt;/a&gt; to see if your framework is already supported.&lt;/li&gt;
&lt;li&gt;If you use a different framework, but your application adheres to the &lt;a href="https://www.w3.org/WAI/standards-guidelines/aria/"&gt;WAI-ARIA standards for web accessibility&lt;/a&gt; by marking components with &lt;code&gt;role&lt;/code&gt; and &lt;code&gt;aria-*&lt;/code&gt; attributes, QF-Test will use these attributes to automatically configure the CustomWebResolver. The better the accessibility of your UI, the better QF-Test will work with it – a double win!&lt;/li&gt;
&lt;li&gt;Otherwise, you&amp;#8217;ll need to write your own CWR mappings. This is not super-difficult, but it requires some knowledge of the HTML structure of your application. Don&amp;#8217;t worry, QF-Test does a lot to help you with this. QF-Test comes with &lt;a href="https://www.qftest.com/doc/manual/en/tech_custom_web_resolver.html#sec_custom_web_resolver"&gt;a comprehensive user interface&lt;/a&gt; which helps you at every step when editing and verifying your configuration. And with the &lt;a href="https://www.qftest.com/doc/manual/en/user_componentinspection.html#usec_inspector"&gt;QF-Test UI Inspector&lt;/a&gt;, you can see the effect of your CWR configuration in real-time. And if you&amp;#8217;re still having trouble, &lt;a href="https://services.qftest.com/en/support/request/"&gt;our support team is ready to assist you&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;button class="plain" onclick="document.getElementById('7859').showModal()" title="Enlarge"&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;img src="https://www.qftest.com/doc/manual/en/images/cwr_gutter.png"
        class=" scroll-animated"
        alt=""
        style=" "&gt;&lt;figcaption&gt;&lt;p&gt;A screenshot of the Edit menu in the QF-Test CustomWebResolver node. It includes all kinds of contextual actions to help you generate a valid CWR configuration in YAML format.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;/button&gt;&lt;dialog popover id="7859" anchor="trigger-7859"&gt;&lt;/p&gt;
&lt;div class="container card scroll-animated width-full accent    "&gt;&lt;div class="container-content"&gt;        &lt;button onclick="document.getElementById('7859').close()"
                class="button blue tertiary"
                id="trigger-7859"
                style="margin: -15px -15px 10px auto; position: relative"&gt;Close&lt;/button&gt;
            &lt;figure&gt;
        &lt;img class="plain" src="https://www.qftest.com/doc/manual/en/images/cwr_gutter.png" alt=""&gt;&lt;figcaption&gt;&lt;p&gt;A screenshot of the Edit menu in the QF-Test CustomWebResolver node. It includes all kinds of contextual actions to help you generate a valid CWR configuration in YAML format.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
    &lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;/dialog&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;We believe that semantic components and the CustomWebResolver are the best way to build reliable, robust and comprehensive UI tests for web applications. And &lt;a href="/en/company/references/evaluation-reports.html"&gt;our customers think so, too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have been using QF-Test for the web already but have not made full use of the power of the CustomWebResolver yet, you should give it a try. And if you are totally new to QF-Test, you can get started with &lt;a href="https://services.qftest.com/en/license/request/"&gt;a free trial&lt;/a&gt; today!&lt;/p&gt;
&lt;h2&gt;Read more&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The CustomWebResolver in the QF-Test manual: &lt;a href="https://www.qftest.com/doc/manual/en/tech_customajax.html#sec_customajax"&gt;Improving component recognition with a CustomWebResolver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tutorial for configuring your first CustomWebResolver: &lt;a href="https://www.qftest.com/doc/manual/en/tech_customajax_demo.html#sec_customajax_demo"&gt;Example for &amp;#8220;CarConfigurator Web&amp;#8221; demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;List of frameworks with built-in CWR: &lt;a href="https://www.qftest.com/doc/manual/en/tech_ajax.html#sec_ajax"&gt;Special support for various web frameworks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Detailed documentation of everything the CWR can do: &lt;a href="https://www.qftest.com/doc/manual/en/tech_custom_web_resolver.html#sec_custom_web_resolver"&gt;The Install CustomWebResolver node&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/><category term="testing"/></entry><entry><title>Objects in QF-Test variables – an overview</title><link href="https://www.qftest.com/en/blog/article/objects-in-qf-test-variables.html" rel="alternate"/><published>2025-02-20T00:00:00+01:00</published><updated>2025-02-20T00:00:00+01:00</updated><author><name>Pascal Bihler</name></author><id>tag:www.qftest.com,2025-02-20:/en/blog/article/objects-in-qf-test-variables.html</id><summary type="html">&lt;p&gt;QF-Test 9 introduces object variables. This new feature has an effect on many parts of QF-Test. This article presents an overview of the most important new features.&lt;/p&gt;</summary><content type="html">&lt;p&gt;When working with variables in QF-Test, the biggest challenge so far has been to understand which variable is currently active with which value. Important here are the variable stacks – &lt;a href="https://www.qftest.com/doc/manual/en/user_variables.html#usec_var_lookup"&gt;the manual explains their function in detail&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The status quo up to QF-Test 9&lt;/h2&gt;
&lt;p&gt;The values stored in variables used to be strings always. Other objects (e.g. numbers) were converted to a string when stored in a variable with &lt;code&gt;rc.setLocal&lt;/code&gt; or &lt;code&gt;rc.setGlobal&lt;/code&gt; and stored in the QF-Test main process. When read with &lt;code&gt;rc.getInt&lt;/code&gt; or &lt;code&gt;rc.getBool&lt;/code&gt; the value was then translated back.&lt;/p&gt;
&lt;p&gt;Using this mechanism it is also possible to set a variable in an &lt;a href="https://www.qftest.com/doc/manual/en/control.html#step_ClientScriptStep"&gt;SUT script&lt;/a&gt; and then use its value in a &lt;a href="https://www.qftest.com/doc/manual/en/control.html#step_ServerScriptStep"&gt;server script&lt;/a&gt; or an &lt;a href="https://www.qftest.com/doc/manual/en/control.html#step_IfSequence"&gt;If node&lt;/a&gt; (and vice versa). If you wanted to cache complex objects, you always had to work with the &lt;a href="https://www.qftest.com/doc/manual/en/user_scripting.html#usec_scriptvars"&gt;&amp;#8220;global&amp;#8221; script variables&lt;/a&gt; of a scripting language, and across process boundaries you had to use &lt;code&gt;rc.fromSUT&lt;/code&gt;and &lt;code&gt;rc.toSUT&lt;/code&gt; or &lt;code&gt;rc.fromServer&lt;/code&gt; and &lt;code&gt;rc.toServer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After more than 20 years this simple understanding of values has reached its limits and has been extended with QF-Test 9: it is now possible to store arbitrary objects in QF-Test variables and thus return complex objects from a procedure, for example.&lt;/p&gt;
&lt;h2&gt;Ensuring backwards compatibility&lt;/h2&gt;
&lt;p&gt;First things first: We have invested a lot of effort to ensure backwards compatibility. The preexisting access methods in the run context return the same types, values can be stored in the same way and when things get complicated (for example when a non-serializable or very large object from an SUT script wants to be stored in a QF-Test variable), QF-Test falls back to the previously used string representation.&lt;/p&gt;
&lt;h2&gt;Object variables in variable tables&lt;/h2&gt;
&lt;p&gt;In QF-Test you can tell right away in any variables table (e.g. in the &lt;a href="https://www.qftest.com/doc/manual/en/user_variables.html#sec_N77251"&gt;options dialog&lt;/a&gt; or in the &lt;a href="https://www.qftest.com/doc/manual/en/user_debugging.html#usec_debugger"&gt;debugger&lt;/a&gt; window) whether a variable value is a string or has a different type: In the first case, everything looks the same as before, i.e. only the string appears in the table. In the second case, the variable value is preceded by the type of the object in parentheses, e.g. &lt;code&gt;(Boolean) true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The type of a stored variable can also be changed here via the context menu.&lt;/p&gt;
&lt;h2&gt;Object variables in &amp;#8220;Set variable&amp;#8221;, &amp;#8220;Return&amp;#8221; and parameter tables&lt;/h2&gt;
&lt;p&gt;In case of the &lt;a href="https://www.qftest.com/doc/manual/en/miscnodes.html#step_SetGlobalStep"&gt;&amp;#8220;Set variable&amp;#8221; node&lt;/a&gt; and the &lt;a href="https://www.qftest.com/doc/manual/en/procedures.html#step_ReturnStep"&gt;&amp;#8220;Return&amp;#8221; node&lt;/a&gt; in a procedure, the type of the set or returned variable can now also be explicitly specified. This is done at runtime, i.e. when the node is executed, the character string specified as the value in the node is converted into the selected type.&lt;/p&gt;
&lt;p&gt;In the same way, the type of the parameter can also be defined in &lt;a href="https://www.qftest.com/doc/manual/en/user_gui.html#usec_tables"&gt;parameter tables&lt;/a&gt;, for example in sequences – here too, the conversion takes place when the node is executed. The value of the parameter then appears in the table again with a type reference, for example: &lt;code&gt;(as Number) 3.141&lt;/code&gt;. The &amp;#8220;as&amp;#8221; indicates that the conversion takes place at runtime.&lt;/p&gt;
&lt;p&gt;Such a conversion can also be carried out everywhere a variable expansion takes place with the help of the &lt;a href="https://www.qftest.com/doc/manual/en/user_variables.html#usec_externaldata"&gt;special group &amp;#8220;as&amp;#8221;&lt;/a&gt;, the syntax looks like &lt;code&gt;${as:fromjson:[1,2,3,4]}&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Object variables via &lt;code&gt;rc.getObj&lt;/code&gt; and &lt;code&gt;rc.vars&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;rc.getObj&lt;/code&gt; method can now be used to read the object value of a variable in a script or script expression. Analogous to the existing methods such as &lt;code&gt;rc.getStr&lt;/code&gt;, &lt;code&gt;rc.getNum&lt;/code&gt; or &lt;code&gt;rc.getBool&lt;/code&gt;, the variable (with one parameter) or a group variable (property, with two parameters) is evaluated here and its value returned – in this case without conversion.&lt;/p&gt;
&lt;p&gt;An expansion of nested variable expressions in the value only takes place automatically if the value is a string, otherwise it must be explicitly requested via the parameter &lt;code&gt;expand=true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://www.qftest.com/doc/manual/en/tech_scripting.html#sec_api_rc"&gt;run context&lt;/a&gt; now also offers a practical shortcut to any variable object: instead of &lt;code&gt;rc.getObj("variablename")&lt;/code&gt; you can now simply write &lt;code&gt;rc.vars.variablename&lt;/code&gt;, and &lt;code&gt;rc.groups.qftest.suite.file&lt;/code&gt; returns the &lt;code&gt;File&lt;/code&gt; object of the current test suite.&lt;/p&gt;
&lt;h2&gt;Object variables and &lt;code&gt;$(name)&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;When you use the familiar &lt;code&gt;$&lt;/code&gt; notation for &lt;a href="https://www.qftest.com/doc/manual/en/user_variables.html#usec_variables"&gt;variables&lt;/a&gt;, the object value of the variable is used for as long as possible: if the variable &lt;code&gt;average&lt;/code&gt; has the value &lt;code&gt;(double) 2.3&lt;/code&gt;, for example, the new variable would also have the type &lt;code&gt;double&lt;/code&gt; for a &amp;#8220;Set variable&amp;#8221; node with the default value &lt;code&gt;$(average)&lt;/code&gt;. However, if you combine the variables with further text, e.g. &lt;code&gt;My average: $(average)&lt;/code&gt;, the string representation of the variable value is automatically used and the new variable becomes a string, as well.&lt;/p&gt;
&lt;p&gt;As before, you cannot simply write &lt;code&gt;$(name) == "John"&lt;/code&gt; as a check expression in an &lt;a href="https://www.qftest.com/doc/manual/en/control.html#step_IfSequence"&gt;If node&lt;/a&gt; – the correct syntax would be &lt;code&gt;"$(name)" == "John"&lt;/code&gt;. But script expressions are preferably used without the &lt;code&gt;$&lt;/code&gt; notation, so it&amp;#8217;s better to use &lt;code&gt;rc.getStr("name") == "John"&lt;/code&gt; – or now &lt;code&gt;rc.vars.name == "John"&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Various small improvements&lt;/h2&gt;
&lt;p&gt;These changes to the QF-Test variable system also allowed us to integrate other long-awaited features into QF-Test 9: The standard procedures and the special &lt;a href="https://www.qftest.com/doc/manual/en/user_variables.html#usec_externaldata"&gt;&amp;#8220;qftest&amp;#8221;-group&lt;/a&gt; now return their values as objects, as seen above.&lt;/p&gt;
&lt;p&gt;It is now also possible in many places to hide values in variables using the context menu and only evaluate them again at runtime with &lt;code&gt;${decrypt:...}&lt;/code&gt; without the true values appearing in the run log.&lt;/p&gt;
&lt;p&gt;Also the &lt;a href="https://www.qftest.com/doc/manual/en/processes.html#step_ProcessClientStarter"&gt;&amp;#8220;Start process&amp;#8221; node&lt;/a&gt; now accepts a list object as a parameter, which makes it possible to implement start nodes with a variable number of parameters much more easily.&lt;/p&gt;
&lt;p&gt;These are small things, but they all contribute to making working and testing with QF-Test even more comfortable.&lt;/p&gt;
&lt;h2&gt;Outlook&lt;/h2&gt;
&lt;p&gt;If you have made it this far, you will have noticed that the topic is complex and certainly cannot be covered exhaustively here.&lt;/p&gt;
&lt;p&gt;The developers behind QF-Test will provide details in an upcoming special webinar on the topic of object variables – and the &lt;a href="https://www.qftest.com/doc/manual/en/manual.html"&gt;newly redesigned QF-Test manual&lt;/a&gt; also explains variables in detail.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>From a simple email to a memorable internship</title><link href="https://www.qftest.com/en/blog/article/from-a-simple-email-to-a-memorable-internship.html" rel="alternate"/><published>2025-02-10T00:00:00+01:00</published><updated>2025-02-10T00:00:00+01:00</updated><author><name>Maxime Mouffron</name></author><id>tag:www.qftest.com,2025-02-10:/en/blog/article/from-a-simple-email-to-a-memorable-internship.html</id><summary type="html">&lt;p&gt;In my 21 years I never left France before. And suddenly, I am driving a thousand kilometers to live in Germany for three months. How did this happen?&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my 21 years, I never left France before. And suddenly, I am driving a thousand kilometers to live in Germany for three months. How did this happen?&lt;/p&gt;
&lt;p&gt;Soon after I began working with QF-Test at Thales, and since I am the only person on my team working with this software, I decided that it might be fun to learn more about QF-Test directly next to the people who built it. So, I sent an email, and a few days later, I got an interview with QFS.&lt;/p&gt;
&lt;p&gt;It only took a week or two to get a definitive answer, and I left for Germany on the 30th of July. Of course – because I like stupid challenges – I decided to drive to Leipzig, from Paris, on my motorcycle. 14 hours of riding later, through rain and sunshine, I finally arrived in Leipzig. Two days later, I started working.&lt;/p&gt;
&lt;p&gt;Working at QFS was a really enriching and weird (in a good way) experience. The work culture is completely different from what I am used to, whether it&amp;#8217;d be at Thales or other smaller companies. Coffee break? 3 minutes. &lt;em&gt;3 minutes!?&lt;/em&gt; Fastest I ever managed was 10! Maybe we are a bit too much into coffee in France. Or is it just an excuse not to work? I&amp;#8217;ll let you decide. Same goes for the lunch break. If we go out in France, at a restaurant, it&amp;#8217;ll be for at least an hour and a half. In Germany, 30 minutes is all you need. These are only small things, but it shocked me how much we love not to work during working hours in France.&lt;/p&gt;
&lt;p&gt;About the actual work: I have never seen such a good cohesion in a team as at QFS. Communication was smooth, not too many meeting, so we actually had time to work. And the team is awesome. Found a bug? Have an issue? A question? Just ask or talk about it, and you&amp;#8217;ll get the answer, or the bug fixed. And even as an intern, a new person working at the company, the onboarding was pretty smooth. Everything is done logically and is easily understandable. It didn&amp;#8217;t take me more than a week to understand most of my job and start working. This feeling of being ready and useful very quickly was extremely satisfying and gave me a lot of motivation to perform my tasks.&lt;/p&gt;
&lt;p&gt;The tasks were satisfying as well. To be given a task, some time, and some liberties to do it, without having a strict deadline, is really comfortable. And when it is done, and everybody is happy about it, it feels even better. I don&amp;#8217;t have the words to explain it better, but trust me, it was a pleasure working at QFS.&lt;/p&gt;
&lt;p&gt;This internship also allowed me to visit and live in a foreign country. As a first timer, this was a bit difficult. Going to Aldi, the barber, or the bakery for the first time felt like I had never done it before. But in the end, it is just like everywhere else. It is &lt;em&gt;not that hard&lt;/em&gt;. I also got to visit the countryside, and I even made a rider friend along the way. I got to ride very fast, to see incredible landscapes (The Zugspitze with Gregor and Tina!), towns, to feel the german weather (not the best…) and to meet new people.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Maxime on his favorite mode of transportation" src="/blog/resources/maxim-2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Even though I loved my stay in Germany, going back to France was a relief. I got my habits back, my birds, my cars, my gym, everything. But I would go back to Germany if given the opportunity, no doubt.&lt;/p&gt;
&lt;p&gt;In conclusion: QFS is an amazing company, leaded by amazing people, and I understand why it is considered such &lt;a href="/en/company/jobs/great-place-to-work.html"&gt;a great place to work&lt;/a&gt;. I learned a lot, and am ready to get QF-Test operational at Thales very soon, as well as making it more widely used across our different teams.&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Extending the QF-Test Assertion API – A practical introduction</title><link href="https://www.qftest.com/en/blog/article/extending-the-qf-test-assertion-api.html" rel="alternate"/><published>2025-01-14T00:00:00+01:00</published><updated>2025-01-14T00:00:00+01:00</updated><author><name>Pascal Bihler</name></author><id>tag:www.qftest.com,2025-01-14:/en/blog/article/extending-the-qf-test-assertion-api.html</id><summary type="html">&lt;p&gt;The API of the fluid check expressions in QF-Test is well equipped so that most checks can be written very easily. In some special cases, however, you may wish to extend the API, as is possible with chai.JS plugins. In this post, we show you how.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Since QF-Test 9 there is a new and comfortable way to check conditions in scripts: The &lt;a href="https://www.qftest.com/doc/manual/de/tech_scripting.html#sec_qfaa"&gt;QF-Test Assertion API&lt;/a&gt;, which is loosely based on the &lt;a href="https://www.chaijs.com/"&gt;chai.JS library known from Javascript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a Groovy script, for example, we can now write:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;beverages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;tea:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;chai&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;matcha&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;oolong&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;be&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;String&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;should&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;be&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;have&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lengthOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beverages&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;have&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tea&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lengthOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The API of the fluid check expressions is well equipped so that most checks can be written very easily. In some special cases, however, you may wish to extend the API, as is possible with &lt;a href="https://www.chaijs.com/plugins/"&gt;chai.JS plugins&lt;/a&gt;. For example, it would be nice if you could use the following expression to check whether a given string represents a valid JSON object:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;validJson&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[1,2,3,&amp;quot;s&amp;quot;,{}&amp;quot;key&amp;quot;:&amp;quot;value&amp;quot;}]&amp;#39;&lt;/span&gt;
&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invalidJson&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[{]&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;validJson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;should&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;be&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;invalidJson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;should&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;not&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;be&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;QF-Test offers an interface for such extensions of the Assertions API as well, which we would like to demonstrate in this blog post.&lt;/p&gt;
&lt;p&gt;To enable a dynamic extension of the API, a proxy object is created under the hood for each assertion whenever a test expression is executed. In addition to the standard methods of each assertion, this proxy also supports any methods that have been registered as extensions in the meantime. An interface definition in the form of an interface is first required for these extensions:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.assertions.external&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.assertions.Assertion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;JsonAssertion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Assertion&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="n"&gt;Assertion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Please note that&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the interface is normally placed in the &lt;code&gt;en.qfs.lib.assertions.external&lt;/code&gt; package,&lt;/li&gt;
&lt;li&gt;it extends the existing &lt;code&gt;Assertion&lt;/code&gt; interface,&lt;/li&gt;
&lt;li&gt;and that each new API method in turn returns the assertion object itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes it possible to chain the method calls. The code to be executed can now be defined in a separate class that implements the interface and derives it from &lt;code&gt;en.qfs.lib.assertions.AssertionExtension&lt;/code&gt;. However, it is easier to do this directly in the interface definition as a &lt;code&gt;default&lt;/code&gt; implementation:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Assertion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json&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="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The implementation of the method does not yet do very much, it merely returns a reference to its own assertion object, as required. To perform or report the actual check, the inherited method &lt;code&gt;doAssert(condition, message, negatedMessage, expected, actual)&lt;/code&gt; must be called:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;condition&lt;/code&gt; is a boolean value or a function with a boolean return value that contains or returns the result of the check.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;message&lt;/code&gt; is a string that can be used for an error message or for the check performed in QF-Test &amp;ndash; the placeholder &lt;code&gt;#{this}&lt;/code&gt; is replaced by the checked object, &lt;code&gt;#{exp}&lt;/code&gt; by the expected value and &lt;code&gt;#{act}&lt;/code&gt; by the actual value (usually identical to the checked object).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;negatedMessage&lt;/code&gt; contains the message that is used for checks whose call chain contains a &lt;code&gt;not&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expected&lt;/code&gt; contains the expected value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;actual&lt;/code&gt; contains the actual value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the exception of &lt;code&gt;condition&lt;/code&gt;, all parameters are optional and can be omitted at the end of the parameter list.&lt;/p&gt;
&lt;p&gt;Our assertion method might now look like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Assertion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;doAssert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;isObjectJson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected #{this} to be a json string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected #{this} not to be json string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We have outsourced the actual check to a separate method in which we try to read the input as JSON and return &lt;code&gt;true&lt;/code&gt; if successful and &lt;code&gt;false&lt;/code&gt; if an error occurs:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;isObjectJson&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="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&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="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_obj&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;instanceof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&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="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&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="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParseException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&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="w"&gt;    &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the expression &lt;code&gt;_obj()&lt;/code&gt; was used to access the object to be checked.&lt;/p&gt;
&lt;p&gt;In order to be able to use our new method in check expressions, it must be registered once with the &lt;code&gt;AssertionFactory&lt;/code&gt;. This factory is a singleton and provides the instance method &lt;code&gt;registerExtension(extension, overwrite)&lt;/code&gt; for this purpose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;extension&lt;/code&gt; is the new assertion interface of the extension. Either a class that implements the interface or an object of such a class can be specified here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;overwrite&lt;/code&gt; determines how to proceed with already registered methods of the same name. If &lt;code&gt;true&lt;/code&gt;, the method of the newly registered extension is preferred, with &lt;code&gt;false&lt;/code&gt; the previously registered one is.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The easiest way to register an extension is to do so when initializing a QF-Test plugin. &lt;a href="/en/blog/article/introduction-to-qf-test-plugin-development.html"&gt;A general description of how to build QF-Test plugins can be found in the previous blog article&lt;/a&gt;. For the plugin that provides our Assertion API extension, the corresponding plugin class could look like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.shared.QFTestPlugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.assertions.AssertionFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.assertions.external.JsonAssertion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JsonAssertionPlugin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QFTestPlugin&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="w"&gt;    &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;AssertionFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&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="na"&gt;registerExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonAssertion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&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="w"&gt;    &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want to add &amp;#8220;properties&amp;#8221; to the call chain, please note that the associated methods must each be preceded by a &lt;code&gt;get&lt;/code&gt; &amp;ndash; the script engines &lt;em&gt;Jython&lt;/em&gt; or &lt;em&gt;Groovy&lt;/em&gt; then automatically convert the properties in the check expression into the corresponding method call.&lt;/p&gt;
&lt;p&gt;As an example, we want to extend our new API to include the check for &lt;a href="https://datatracker.ietf.org/doc/html/rfc8259#section-1"&gt;&amp;#8220;primitive&amp;#8221; JSON objects&lt;/a&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;primitiveObj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;String&amp;quot;&amp;#39;&lt;/span&gt;
&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nonPrimitiveObj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[1,2,3]&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;primitiveObj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;should&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;be&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primitive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;nonPrimitiveObj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;should&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;not&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;be&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;primitive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we add the corresponding &lt;code&gt;getPrimitive&lt;/code&gt; method to our &lt;code&gt;JsonAssertion&lt;/code&gt; interface:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Assertion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getPrimitive&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;primitive&amp;quot;&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="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and add the test method:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;isObjectJson&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="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;primitive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AssertionUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flagAsBoolean&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;primitive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&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="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_obj&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;instanceof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&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="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JsonValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primitive&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="w"&gt;                &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;valueIsObjectOrArray&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;valueIsObjectOrArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&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="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&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="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParseException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&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="w"&gt;    &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A typical Assertion API scheme is used here: in the property method, a status value is set in the assertion object with &lt;code&gt;self().flag(name, value)&lt;/code&gt;, which is then retrieved during evaluation. To achieve this, the &lt;code&gt;self().flag(name)&lt;/code&gt; method returns the corresponding status value. To simplify matters, the class &lt;code&gt;en.qfs.lib.assertions.AssertionUtil&lt;/code&gt; offers the static helper methods &lt;code&gt;flagAsBoolean(assertion, name)&lt;/code&gt; and &lt;code&gt;flagAsString(assertion, name)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The message for the error case should also be adjusted to reflect the status:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Assertion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json&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="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;primitive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AssertionUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flagAsBoolean&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;primitive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;primitive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;primitive &amp;quot;&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="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;doAssert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;isObjectJson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected #{this} to be a &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;json string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected #{this} not to be &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;json string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;self&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, in order to hide the internal details in the stack trace of the &lt;code&gt;AssertionError&lt;/code&gt; thrown in the event of an error, the test method can define itself as an entry point (&lt;em&gt;ssfi&lt;/em&gt; stands for &lt;em&gt;start stack function indicator&lt;/em&gt;)):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Assertion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_ssfi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonAssertion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;json&amp;quot;&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note: Due to the class loader used, extensions are currently restricted to &lt;strong&gt;Server&lt;/strong&gt; scripts and &lt;strong&gt;Groovy SUT&lt;/strong&gt; scripts.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://gitlab.com/qfs/qftest-plugins"&gt;code of the entire extension is available on GitLab&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Introduction to QF-Test Plugin Development</title><link href="https://www.qftest.com/en/blog/article/introduction-to-qf-test-plugin-development.html" rel="alternate"/><published>2025-01-09T00:00:00+01:00</published><updated>2025-01-09T00:00:00+01:00</updated><author><name>Pascal Bihler</name></author><id>tag:www.qftest.com,2025-01-09:/en/blog/article/introduction-to-qf-test-plugin-development.html</id><summary type="html">&lt;p&gt;In this blog post we unveil the full potential of QF-Test plugins.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Some people may have stumbled over &lt;a href="https://www.qftest.com/doc/manual/en/tech_scripting.html#sec_plugindir"&gt;the &lt;code&gt;plugin&lt;/code&gt; directory in QF-Test&lt;/a&gt; and wondered why it is there and how to use it. In fact, the directory (with its subdirectories &lt;code&gt;qftest&lt;/code&gt; and &lt;code&gt;sut&lt;/code&gt;) serves multiple purposes:&lt;/p&gt;
&lt;p&gt;Upfront, if you place any &lt;code&gt;.jar&lt;/code&gt; file containing Java classes in that directory, they will be automatically added to the QF-Test and/or SUT classpath and therefore available for use in server or SUT script nodes, in data driver nodes (e.g. JDBC drivers) etc. If the library is placed in the &lt;code&gt;qftest&lt;/code&gt; subdirectory, the contained classes are only available within the main QF-Test process and server scripts, but not within the classpath of the tested application. If you place the library in &lt;code&gt;sut&lt;/code&gt;, you can use it within SUT scripts only. A library placed directly in &lt;code&gt;plugin&lt;/code&gt; is available in both context.&lt;/p&gt;
&lt;p&gt;In addition, it is possible to enhance the QF-Test test execution itself, or to add special functionality. Unforgotten is the &lt;a href="https://www.youtube.com/watch?v=qvroe4d-nxg"&gt;holistic plugin&lt;/a&gt;, which caused QF-Test to play &amp;#8220;characteristic&amp;#8221; sounds during test execution so users could listen to their tests running. While this example may be rather playful, we will see how to use this mechanism for more serious tasks.&lt;/p&gt;
&lt;p&gt;With QF-Test 8 and the &lt;a href="https://plugins.gradle.org/plugin/de.qfs.qftest"&gt;QF-Test Grade plugin 2.1.0&lt;/a&gt; we made the development of QF-Test plugins very comfortable &amp;ndash; it&amp;#8217;s time to take a deep dive into the topic.&lt;/p&gt;
&lt;p&gt;In this example, we will build add a plugin which extends QF-Test with an embedded web server that displays the current execution state. The final result can be found at &lt;a href="https://gitlab.com/qfs/qftest-plugins/webstatus-plugin"&gt;gitlab.com/qfs/qftest-plugins/webstatus-plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Technically, QF-Test plugins are also &lt;code&gt;.jar&lt;/code&gt; files which are placed in the &lt;code&gt;plugin&lt;/code&gt; folder. But they contain a specific pointer file in &lt;code&gt;META-INF/services&lt;/code&gt; which allows QF-Test to load the plugin class at runtime.&lt;/p&gt;
&lt;p&gt;For development, any IDE which supports Gradle builds can be used, for example IntelliJ. To follow along, create a new project, name it &lt;code&gt;webstatus&lt;/code&gt;, select &lt;em&gt;Gradle&lt;/em&gt; as build system and make sure that at least Java 17 is selected as JDK.&lt;/p&gt;
&lt;p&gt;&lt;img alt="New Project view in IntelliJ" src="/blog/resources/plugindev/1-new_project.png" /&gt;&lt;/p&gt;
&lt;p&gt;As a first step, we add the QF-Test Gradle plugin to the project by adding it to the &lt;code&gt;plugins&lt;/code&gt; section of the &lt;code&gt;build.gradle&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;java&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;de.qfs.qftest&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2.1.0&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If we now reload the project (e.g. using the reload button in the &amp;#8220;Gradle&amp;#8221; IntelliJ panel), Gradle will automatically download the latest QF-Test and add it to the project&amp;#8217;s &lt;code&gt;testImplementation&lt;/code&gt; dependencies.&lt;/p&gt;
&lt;p&gt;&lt;img alt="IntelliJ Gradle view" src="/blog/resources/plugindev/2-reload-gradle.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can instruct the plugin to use the QF-Test installation you already have on your system by adding the &lt;code&gt;version = 'local'&lt;/code&gt; property in a new &lt;code&gt;qftest&lt;/code&gt; section of the &lt;code&gt;build.gradle&lt;/code&gt; file (If you did not install QF-Test at the default location, you can use the &lt;code&gt;versionDir&lt;/code&gt; property instead, as described in the &lt;a href="https://gitlab.com/qfs/qftest-gradle-plugin/-/tree/main/DemoProject#optional-gradlebuild-configuration"&gt;documentation of the QF-Test Gradle Plugin&lt;/a&gt;):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;qftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;local&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Until now, the instructions have been similar to any project which uses the QF-Test Gradle plugin for test execution. To put the project into &amp;#8220;QF-Test plugin development mode&amp;#8221;, we need to add another property to the &lt;code&gt;qftest&lt;/code&gt; section of &lt;code&gt;build.gradle&lt;/code&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;qftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;local&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pluginClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;de.qfs.apps.qftest.plugins.WebStatusPlugin&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In our example &lt;code&gt;de.qfs.apps.qftest.plugins.WebStatusPlugin&lt;/code&gt; is the fully qualified class name of the plugin class we&amp;#8217;re about to develop. Since the QF-Test plugin will only be available in the QF-Test main process, we can tell the Gradle plugin to focus on this development environment:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;qftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;local&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pluginClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;de.qfs.apps.qftest.plugins.WebStatusPlugin&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;devScopes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;qftest&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// default: [&amp;#39;qftest&amp;#39;,&amp;#39;sut&amp;#39;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After having setup the build system, we can start developing the plugin itself: Let&amp;#8217;s create a new class and let it implement &lt;code&gt;de.qfs.apps.qftest.shared.QFTestPlugin&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Skeleton of the plugin class" src="/blog/resources/plugindev/3-empty-class.png" /&gt;&lt;/p&gt;
&lt;p&gt;Since the Gradle plugin automatically extended the &lt;code&gt;implementation&lt;/code&gt; dependencies, the required QF-Test classes are directly available using code completion. As a first step, we simply add a &amp;#8220;Hello World&amp;#8221; output:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hello plugin world!&amp;quot;&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To start up QF-Test including the plugin, we can use the &lt;code&gt;startQFTest&lt;/code&gt; task of Gradle &amp;ndash; the Gradle Plugin will automatically take care of creating the required &lt;code&gt;services&lt;/code&gt; file and make the new class available to QF-Test:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;./gradlew&lt;span class="w"&gt; &lt;/span&gt;startQFTest
Starting&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;Gradle&lt;span class="w"&gt; &lt;/span&gt;Daemon

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Task&lt;span class="w"&gt; &lt;/span&gt;:startQFTest
Starting&lt;span class="w"&gt; &lt;/span&gt;QF-Test&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

BUILD&lt;span class="w"&gt; &lt;/span&gt;SUCCESSFUL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;7s
&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;actionable&lt;span class="w"&gt; &lt;/span&gt;tasks:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;executed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Our plugin class is compiled, QF-Test starts up &amp;ndash; but no output! What went wrong?&lt;/p&gt;
&lt;p&gt;By default, the &lt;code&gt;startQFTest&lt;/code&gt; task is used to start QF-Test interactively in a &amp;#8220;fire-and-forget&amp;#8221; manner &amp;ndash; no outputs are attached and the Gradle task does not wait until QF-Test is closed. To see the debug output, we must change the &lt;code&gt;fork&lt;/code&gt; property of the task, by adding this new definition to the &lt;code&gt;build.gradle&lt;/code&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;startQFTestAndWait&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StartQFTestTask&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, we use this new task to start QF-Test, and our output becomes visible:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;%&lt;span class="w"&gt; &lt;/span&gt;./gradlew&lt;span class="w"&gt; &lt;/span&gt;startQFTestAndWait

&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Task&lt;span class="w"&gt; &lt;/span&gt;:startQFTestAndWait
Starting&lt;span class="w"&gt; &lt;/span&gt;QF-Test&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

Hello&lt;span class="w"&gt; &lt;/span&gt;plugin&lt;span class="w"&gt; &lt;/span&gt;world!
&amp;lt;&lt;span class="o"&gt;=========&lt;/span&gt;----&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;EXECUTING&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;17s&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;:startQFTestAndWait
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;init()&lt;/code&gt; method is called very early during the startup of QF-Test or the SUT, as soon as the class has been loaded. You can distinguish different execution contexts by calling the static methods &lt;code&gt;isRunningInSUT()&lt;/code&gt; (i.e. SUT (&lt;code&gt;true&lt;/code&gt;) or QF-Test (&lt;code&gt;false&lt;/code&gt;)) and &lt;code&gt;isRunningInteractively()&lt;/code&gt; (interactive (&lt;code&gt;true&lt;/code&gt;) or batch mode (&lt;code&gt;false&lt;/code&gt;)) of the class &lt;code&gt;de.qfs.apps.qftest.shared.Util&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to delay execution until QF-Test has loaded or the SUT has been connected, you can listen for the &lt;code&gt;"qftest-loaded"&lt;/code&gt; or &lt;code&gt;"sut-loaded"&lt;/code&gt; notifications respectively:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.notifications.DefaultNotificationCenter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.notifications.Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Plugin init&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DefaultNotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;qftestLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;qftest-loaded&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DefaultNotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sutLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sut-loaded&amp;quot;&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="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;qftestLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;QF-Test loaded&amp;quot;&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="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sutLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SUT loaded&amp;quot;&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notifications are a powerful mechanism in QF-Test to send synchronous messages (= well defined Strings with optional additional data) in a decoupled way. The concept is well known &lt;a href="https://developer.apple.com/documentation/foundation/notificationcenter"&gt;from the Apple Foundation framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also use notifications in your QF-Test scripts &amp;ndash; just type &amp;#8220;notifications.&amp;#8221; in a script and press &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Space&lt;/kbd&gt; to get a documentation about the available &lt;code&gt;UserNotifications&lt;/code&gt; and simplified sender and observer methods. We can also use such a &lt;code&gt;UserNotifications&lt;/code&gt; with the notification observer technique &amp;ndash; e.g. to monitor test execution state in a basic fashion (If compilation fails due to a class file version issue, make sure you&amp;#8217;re using at least JDK 17 for your project):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.extensions.qftest.TestRunEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.extensions.qftest.TestSuiteNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.shared.script.modules.UserNotifications&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="o"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebStatusPlugin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QFTestPlugin&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="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TestSuiteNode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeStack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LinkedList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="w"&gt;    &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;DefaultNotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;runStarted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;UserNotifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RUN_STARTED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;DefaultNotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;runStopped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;UserNotifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RUN_STOPPED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;DefaultNotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;nodeEntered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;UserNotifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NODE_ENTERED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;DefaultNotificationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;nodeExited&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;UserNotifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NODE_EXITED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&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="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;runStarted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notification&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;nodeStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;runStopped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notification&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nodeEntered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notification&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;TestRunEvent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestRunEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserInfoValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;event&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;nodeStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNode&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&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="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nodeExited&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notification&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;nodeStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In order to present the current test execution state in the browser, we need an embedded web server delivering this information as a web page. Unfortunately, Java 17 (which is used to execute QF-Test) does not include a simple HTTP server module yet (this has only been introduced with Java 18). But a look in the &lt;code&gt;lib&lt;/code&gt; library of QF-Test reveals that the &lt;a href="https://undertow.io/"&gt;&lt;code&gt;undertow&lt;/code&gt; library&lt;/a&gt; is bundled, which includes a simple HTTP server class. When we add &lt;code&gt;'undertow'&lt;/code&gt; to the &lt;code&gt;devScopes&lt;/code&gt; property, the corresponding library bundled with QF-Test is automatically added to our &lt;code&gt;implementation&lt;/code&gt; dependencies:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;qftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;local&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pluginClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;de.qfs.apps.qftest.plugins.WebStatusPlugin&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;devScopes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;qftest&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;undertow&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With this library, we sketch out a simple &lt;code&gt;WebStatusServer&lt;/code&gt; class which returns the current execution state upon request:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.plugins&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;io.undertow.Undertow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;io.undertow.server.HttpServerExchange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;io.undertow.util.Headers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebStatusServer&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="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WebStatusServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebStatusPlugin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Undertow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Undertow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addHttpListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;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="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;running&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="w"&gt;                        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nodeStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&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="k"&gt;else&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="w"&gt;                            &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Test execution running&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&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="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&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="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Test execution stopped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;}}).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRuntime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addShutdownHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Undertow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ListenerInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listenerInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getListenerInfo&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;QF-Test status available at &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;listenerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProtcol&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;:/&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listenerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAddress&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="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpServerExchange&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResponseHeaders&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REFRESH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResponseSender&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endExchange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We load this web server in the &lt;code&gt;WebStatusPlugin&lt;/code&gt; class as soon as QF-Test has loaded:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;qftestLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notification&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="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WebStatusServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But unfortunately, the start of our plugin now fails:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;% ./gradlew startQFTestAndWait&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;startQFTestAndWait&lt;/span&gt;
&lt;span class="n"&gt;Starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;QF-Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;(local)&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;&lt;span class="c"&gt;) main de.qfs.lib.notifications.DefaultNotification.getDefaultExceptionHandler().ExceptionHandler.handleException(Throwable,Notification,Observer): (#147) exception: java.lang.NoClassDefFoundError: io/undertow/Undertow (...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;undertow&lt;/code&gt; library appears to be bundled by QF-Test, but is not normally available in the default classpath, so we have to do some reflection calls to add the library to the classpath and instantiate it &amp;ndash; luckily the QF-Test &lt;code&gt;de.qfs.lib&lt;/code&gt; package has helper methods for these tasks:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.shared.system.Native&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.util.DynamicClassLoader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.util.Reflector&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="o"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;qftestLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notification&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ClassLoader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClass&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getClassLoader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;DynamicClassLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadJarIntoClassLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;Native&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVersionDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/lib/undertow.jar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;serverClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;de.qfs.apps.qftest.plugins.WebStatusServer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Reflector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serverClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WebStatusPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&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="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Could not start WebStatus server: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now start QF-Test, head over to [http://localhost:9000] and watch our test progress on every automatic reload!&lt;/p&gt;
&lt;p&gt;Finally, to ship this great new QF-Test plugin, we build an all-containing &lt;code&gt;.jar&lt;/code&gt; file using &lt;code&gt;./gradlew jar&lt;/code&gt; and copy the resulting file from the &lt;code&gt;build/libs&lt;/code&gt; folder to the &lt;code&gt;plugin/qftest&lt;/code&gt; folder of QF-Test, so it is available in all our test runs.&lt;/p&gt;
&lt;p&gt;In an upcoming blog post, we&amp;#8217;ll see how easy it is to use the QF-Test plugin mechanism to extend the new QF-Test 8 Assertion API. Until then, let this post inspire your creativity!&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>My 4-Year Experience as a working student at QFS – A Retrospective</title><link href="https://www.qftest.com/en/blog/article/my-4-year-experience-as-a-working-student-at-qfs-a-retrospective.html" rel="alternate"/><published>2024-12-04T00:00:00+01:00</published><updated>2024-12-04T00:00:00+01:00</updated><author><name>Sarah Wiegel</name></author><id>tag:www.qftest.com,2024-12-04:/en/blog/article/my-4-year-experience-as-a-working-student-at-qfs-a-retrospective.html</id><summary type="html">&lt;p&gt;Over the last four years, I had the opportunity to work as a working student at Quality First Software GmbH, where I gained invaluable experience. Looking back, this time was not only educational but also extremely exciting.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Over the last four years, I had the opportunity to work as a working student at Quality First Software GmbH, where &lt;a href="/en/blog/article/my-internship-at-qfs-sorting-and-comparing-xml-files-and-other-things.html"&gt;I gained invaluable experience&lt;/a&gt;. Looking back, this time was not only educational but also extremely exciting. It allowed me to grow professionally and as a person.&lt;/p&gt;
&lt;p&gt;When I started my role at QFS, I already had some theoretical knowledge from my studies, but working on real projects in real life was a completely different experience. From the very beginning, I was involved in ongoing projects and given the chance to actively contribute and bring in my own ideas. This allowed me to deepen my knowledge of various programming languages, investigate new technologies, and continually develop my skills. Thanks to this long-term position, I felt like I was truly contributing to the success of the team and the project.&lt;/p&gt;
&lt;p&gt;One of the most important factors that made my time as a working student so special was the team. My colleagues were not only highly skilled and professional but also incredibly helpful and open. Whether it were technical questions I couldn&amp;#8217;t solve on my own or bringing in new ideas, I always felt supported and appreciated. Regular team events like table football at the office, team sports, and game nights after work strengthened the team spirit and made the work environment very enjoyable.&lt;/p&gt;
&lt;p&gt;Another key aspect was the flexible work-life balance, which was especially helpful during exam periods. In these intense phases, I was able to reduce or rearrange my hours as needed. This consideration was crucial in allowing me to perform consistently well in both my studies and my work.&lt;/p&gt;
&lt;p&gt;In conclusion, I would like to express my heartfelt thanks to everyone for the wonderful time that gave me this valuable experience and the opportunity for personal and professional growth.&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>AI is a Big Topic: 11.14. is World Quality Day</title><link href="https://www.qftest.com/en/blog/article/ai-is-a-big-topic-1111-is-world-quality-day.html" rel="alternate"/><published>2024-11-11T00:00:00+01:00</published><updated>2024-11-11T00:00:00+01:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2024-11-11:/en/blog/article/ai-is-a-big-topic-1111-is-world-quality-day.html</id><summary type="html">&lt;p&gt;Did you know&amp;#8230; that November 14th is World Quality Day? This tradition was started by the United Nations in 1990. It deals with the question of what quality is and what quality standards mean for our work.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Did you know that November 14th is &lt;strong&gt;World Quality Day&lt;/strong&gt;? This tradition was started by the United Nations in 1990. It deals with the question of what quality is and what quality standards mean for our work.&lt;/p&gt;
&lt;p&gt;The task of the &lt;a href="https://www.capgemini.com/insights/research-library/world-quality-report-2023-24/"&gt;World Quality Report by Capgemini and Sogeti&lt;/a&gt; is to answer this question specifically for &lt;em&gt;software&lt;/em&gt; quality. The 15th edition of the report, which has just been published, surveys managers from various Fortune 500 companies in detailed interviews. This year, unsurprisingly, the focus is on generative AI. &lt;a href="https://prod.ucwe.capgemini.com/wp-content/uploads/sites/8/2023/11/WQR_2023_FINAL_WEB_CG.pdf"&gt;Read the current report in full here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, &amp;#8220;quality&amp;#8221; has always been a core concept in our work. But what about QF-Test and AI, especially Large Language Models (LLMs)? In our view, AI can be used sensibly to help achieve goals, but it is not an end in itself. For our customers, QF-Test is always part of a toolchain, and it is not a problem for QF-Test if AI-generated data plays a role in such a chain.&lt;/p&gt;
&lt;p&gt;Useful scenarios for QF-Test can therefore arise when QF-Test is used as part of a GenAI-based toolchain where structured data must be read from program interfaces or can only be entered via such interfaces, i.e. in cases where there is only a UI and no API available. QF-Test can fill such gaps much faster and more reliably than current AI systems can.&lt;/p&gt;
&lt;p&gt;Where there is currently no need for AI support in QF-Test is the critical point of object recognition in an application UI, as QF-Test already recognizes components reliably and plausibly from the user&amp;#8217;s point of view, thanks to its flexible and mature algorithms.&lt;/p&gt;
&lt;h2&gt;World Quality Report 2024/25: Recommendations&lt;/h2&gt;
&lt;p&gt;Here is a brief summary of the &lt;a href="https://www.opentext.com/resources/world-quality-report-2024-25"&gt;key recommendations&lt;/a&gt; from this year&amp;#8217;s World Quality Report for those with responsibilities in today&amp;#8217;s test automation industry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Develop an organization-wide QA automation strategy to achieve consistency and increase cost efficiency.&lt;/li&gt;
&lt;li&gt;Leverage the potential of GenAI to improve and accelerate test automation.&lt;/li&gt;
&lt;li&gt;Future-proof QA automation tools to optimize integration with new technologies.&lt;/li&gt;
&lt;li&gt;Understand that GenAI will not replace your quality engineers, but will significantly increase their productivity.&lt;/li&gt;
&lt;li&gt;Evaluate the contribution of quality engineering to business goals, such as customer satisfaction, impact on revenue and overall product quality.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A little journey through time&lt;/h2&gt;
&lt;p&gt;Back in 2019, the World Quality Report reported that especially in Germany there was a high demand for end-to-end testing, including a high degree of automation. The difficulty is that the faster pace of the transition to agile and DevOps development means that the nature, form and timing of testing needs to evolve. It needs people who are experienced testers on the one hand, but who are also able to express test routines within development phases in program code and who also know how to build end-to-end test automation frameworks.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.capgemini.com/news/press-releases/world-quality-report-2021-22-quality-assurance-is-now-integral-to-software-delivery-as-agility-becomes-non-negotiable/"&gt;In 2021&lt;/a&gt;, an increase in the importance of quality assurance was again noted. The expectations of testing and quality assurance are becoming more realistic, the report concluded. Important focal points within the IT strategy were the improvement of the customer experience, greater security, faster responsiveness to business requirements and the high quality of software solutions. Artificial intelligence (AI), agile practices and DevOps, and in particular the emerging application area of the smart industry, have also had a significant impact on quality assurance and testing.&lt;/p&gt;</content><category term="blog"/><category term="testing"/><category term="ai"/></entry><entry><title>Foosball, Coffee and Code – My three-month internship at QFS</title><link href="https://www.qftest.com/en/blog/article/foosball-coffee-and-code-my-three-month-internship-at-qfs.html" rel="alternate"/><published>2024-09-17T00:00:00+02:00</published><updated>2024-09-17T00:00:00+02:00</updated><author><name>Vincent Helmers</name></author><id>tag:www.qftest.com,2024-09-17:/en/blog/article/foosball-coffee-and-code-my-three-month-internship-at-qfs.html</id><summary type="html">&lt;p&gt;What I experienced, learned and achieved during my time at QFS (besides table football).&lt;/p&gt;</summary><content type="html">&lt;p&gt;3 to 3. Match ball. The players stand in their positions, tense, their hands sweaty. Throw-in. Team Blue has the ball. A targeted pass forward, the forward takes it. A pass to the boards, over the top, shot! The moment in which the ball finds its way towards the goal line stretches out, feels like an eternity. At the last moment, the red goalkeeper jumps in and blocks the ball. There it is already back in midfield. The ball is precisely parried by both teams again and again – until Blue makes a mistake. The ball is now at the feet of the red striker. And there it lies, safe, &lt;em&gt;dangerous&lt;/em&gt;. A breath later, a treacherous press ball is on its way towards the goal. The ball winds its way around the defense, but the blue goalkeeper is standing firm, &lt;em&gt;too firm&lt;/em&gt;. At the last second, the ball takes a small right turn due to the spin. You can hear it clack. GOOOOOAL! The red team wins this match 4 to 3.&lt;/p&gt;
&lt;p&gt;If there was something I hadn&amp;#8217;t expected from my three months at QFS, that would probably have been it. But to only talk about the twice-weekly table football matches in the this post would probably be a gross understatement of what I actually experienced and learned during this time. But first a bit about me:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Vincent Helmers" src="/blog/resources/vincent-praktikum.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I started to take an interest in computer science back in 2020, when I started working on bigger and bigger hobby projects with friends. I heard about QFS for the first time through friends and my computer science teacher, whose former students I eventually got to know at QFS.&lt;/p&gt;
&lt;p&gt;In February 2024, four months before my own A-level exams, I did a one-week internship at QFS, where I was able to gain my first experience with the company and was also introduced to the project which would accompany me throughout the months between my A-levels and my university studies: Video streaming the screen content of a (virtual) Android device via Android Accessibility over the network to a Swing application.&lt;/p&gt;
&lt;p&gt;If you didn&amp;#8217;t quite get that last part, dear reader, don&amp;#8217;t worry: I didn&amp;#8217;t either. But once I understood what this all meant (with a little help, because Android can be &lt;em&gt;so annoying&lt;/em&gt;), I was able to show off a working prototype, which ran on my computer separately from QF-Test, after my first week at QFS.&lt;/p&gt;
&lt;p&gt;A bit of school and A-levels later, I thought to myself – in view of my upcoming computer science studies – that it might make sense to gain a bit more than one week of experience as a &amp;#8220;professional&amp;#8221; developer. Before I knew it I had a three-month temp job at QFS with the task to actually integrate my prototype into QF-Test and migrate it from Android Accessibility to the Android Media Projection API.&lt;/p&gt;
&lt;p&gt;I know, I know: fancy!&lt;/p&gt;
&lt;p&gt;In addition to the actual work as a developer, where I learned, for example, how to find my way around a large project, or that the debugger is quite a useful invention, there were some internal trainings in which, for example, cybersecurity or proper testing was discussed. I was also part of dev meetings that gave me a deeper insight into problems that went completely unnoticed from the outside. There I was able to learn how to approach such tasks in a structured way.&lt;/p&gt;
&lt;p&gt;There was a clear schedule on Monday and Thursday, which gave the day a rhythm and allowed me to talk to my colleagues about topics other than the previous Jira ticket. 9:30 a.m., for example, was the coffee break (other drinks were also available, of course). 15 minutes later (sometimes after a small workout), the stand-up meeting started, during which upcoming tasks and the past weekend were discussed and sometimes a colleague&amp;#8217;s birthday was serenaded. Then came the lunch break. On Mondays, we ate together in the meeting room. On Thursdays, we went over to the Greek or Italian restaurant or &amp;ndash; if both were closed &amp;ndash; to Lieferando. And between 2 and 3 pm, the long-awaited foosball game took place &amp;ndash; a welcome distraction from &lt;em&gt;that line of code that won&amp;#8217;t run&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This created a very pleasant working atmosphere, where you don&amp;#8217;t have to be afraid of a dull office, but can ride your bike full of anticipation from Geretsried to Gelting. I never felt alone with my project and this was one of the reasons why I was able to present another finished prototype at the end of August, which I was definitely proud of.&lt;/p&gt;
&lt;p&gt;So in conclusion, I want to again thank everyone at QFS: thank you very much for this great experience and the nice gifts for both my birthday and my farewell to Passau. The way you do it, IT is really fun. My time with QFS has given me an insight into a functioning, pleasant and productive working life. And who knows, maybe I&amp;#8217;ll be back in the future&amp;#8230;&lt;/p&gt;
&lt;p&gt;P.S.: But only if I survive math at university, I think that will be a challenge of its own.&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Webswing and QFS: A Powerful Alliance for Java Application Migration</title><link href="https://www.qftest.com/en/blog/article/webswing-and-qfs-a-powerful-alliance-for-java-application-migration.html" rel="alternate"/><published>2024-05-12T00:00:00+02:00</published><updated>2024-05-12T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2024-05-12:/en/blog/article/webswing-and-qfs-a-powerful-alliance-for-java-application-migration.html</id><summary type="html">&lt;p&gt;A blog post from our partner Webswing. Explore the potential of this transformative collaboration and see how your organization can benefit from migrating Java applications to the web with Webswing and QF-Test.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We have been working closely with Webswing for several years to bring Swing applications to the web and do it in a safe, well-tested way. &lt;a href="https://www.webswing.org/en/blog/webswing-and-qfs-a-powerful-alliance-for-java-application-migration"&gt;Now, Webswing have written a short blog post&lt;/a&gt; to talk about the benefits of this partnership:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Recognizing the synergy between Webswing and QFS, QFS integrated extensive support for Webswing applications. This integration allows comprehensive testing of Java applications in their native form, as web applications via Webswing, or both concurrently. This makes QF-Test an ideal tool for companies migrating their Java applications to the web.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We are excited about the continuous improvements coming to Webswing and will make sure that QF-Test remains the best way to accompany a migration to Webswing with reliable and easy-to-use end-to-end testing.&lt;/p&gt;
&lt;p&gt;Learn more on the &lt;a href="https://www.webswing.org/en/blog/webswing-and-qfs-a-powerful-alliance-for-java-application-migration"&gt;Webswing blog&lt;/a&gt; or on our &lt;a href="/en/product/qf-test/java-testing/webswing-testing-with-qf-test.html"&gt;&amp;#8220;Integrated Webswing testing&amp;#8221; page&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>JavaFX – More alive than ever</title><link href="https://www.qftest.com/en/blog/article/javafx-more-alive-than-ever.html" rel="alternate"/><published>2024-04-02T00:00:00+02:00</published><updated>2024-04-02T00:00:00+02:00</updated><author><name>Gregor Schmid</name></author><id>tag:www.qftest.com,2024-04-02:/en/blog/article/javafx-more-alive-than-ever.html</id><summary type="html">&lt;p&gt;A few years ago I provocatively asked: &amp;#8220;Is FX even still alive?&amp;#8221; Today my answer would be: &amp;#8220;More alive than ever&amp;#8221;, and that is not a given after the Covid years.&lt;/p&gt;</summary><content type="html">&lt;p&gt;After the JFX Adopters Meeting in late 2021, I asked the provocative question &lt;a href="/en/blog/article/is-fx-even-still-alive-javafx-adopters-meeting-2021.html"&gt;&amp;#8220;Is FX Even Still Alive?&amp;#8221;&lt;/a&gt;. How should the question be answered today? &amp;#8220;JavaFX &amp;ndash; more alive than ever&amp;#8221; is my current answer. After the Covid years, when meetings of the FX community could only take place online, this is not a matter of course.&lt;/p&gt;
&lt;p&gt;It was an impressive demonstration of the diverse activities of the FX world: organized with a lot of passion by Christian Heilmann, we came together at the beginning of March 2024 for the JFX Adopters Meeting at ZEISS in Munich. Meeting in-person felt really good after the Covid years &amp;ndash; and I say that as someone who usually tends to avoid meetings and crowds in general.&lt;/p&gt;
&lt;h2&gt;JFX Adopters Meeting at ZEISS&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://www.zeiss.com/meditec/en/news-events/events.html"&gt;schedule&lt;/a&gt; was tightly packed. I want to take a moment to thank Christian for his impressive commitment to providing the community with a platform for professional exchange. The presentations ranged from live coding and practical examples to integration projects such as embedding Excel tables.&lt;/p&gt;
&lt;p&gt;What else stuck with me? JavaFX impresses with its outstanding security features. I am curious about the latest FX features for version 17-22, rendering in FX as a great technology for 2D and 3D, subtitles for TV using AI&amp;#8230; and much more.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Photo: ZEISS" src="/blog/resources/JFX-adopters-meeting-1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;A good overview of the FX community beyond the presentations can be found on the &lt;a href="https://www.jfx-central.com"&gt;JFX-Central website&lt;/a&gt; (nicely set up with JavaFX and JPro). New projects are added there and in the &lt;a href="https://www.jfx-ensemble.com"&gt;JFX-Ensemble&lt;/a&gt; on an ongoing basis.&lt;/p&gt;
&lt;h2&gt;JavaFX and QF-Test&lt;/h2&gt;
&lt;p&gt;QF-Test has supported testing of JavaFX applications since 2014 (10 year anniversary, I just realized!), at that time inspired by Wolfgang Weigend from Oracle.&lt;/p&gt;
&lt;p&gt;&amp;#8220;Innovations in QF-Test and development of the FX engine compared to others&amp;#8221; was the title of my presentation this year. In short: Of the engines supported by QF-Test (Swing, SWT, Web, Windows, PDF and Android), JavaFX is by far the one with the least support effort. Web at the other end of the list requires &lt;em&gt;much&lt;/em&gt; more support. FX is very stable and has been in use at around 150 customers of ours &amp;ndash; often in combination with other engines &amp;ndash; for many years.&lt;/p&gt;
&lt;p&gt;The be-all, end-all of test automation is stable recognition. I was able to demonstrate the latest QF-Test mechanisms live and in detail &amp;ndash; in particular &lt;a href="/en/blog/article/smartid-the-next-generation-of-component-recognition.html"&gt;SmartID&lt;/a&gt;. Using the example of a car configurator running in parallel on the three platforms Swing, Web and FX, I was able to demonstrate that recording and recognition of components across the technologies works out-of-the-box thanks to SmartID.&lt;/p&gt;
&lt;h2&gt;The future of JavaFX &amp;ndash; and the JFX Adopters Meeting&lt;/h2&gt;
&lt;p&gt;Finally, here are a few other voices with their personal impressions of the JFX Adopters Meeting:&lt;/p&gt;
&lt;p&gt;Christian Heilmann (ZEISS, organizer of the JFX Adopters Meeting):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The JFX Adopters Meeting is the only user event in the world that enables the JavaFX community to meet in person in a safe space. We deliberately refrain from recordings in order to encourage intensive and open discussions during the event.&lt;/p&gt;
&lt;p&gt;The JFX Adopters Meeting has helped us to get in touch with many smart people, to gather the key players of the JavaFX eco-system, to get new inspiration and to learn from each other.&lt;/p&gt;
&lt;p&gt;Having an interface toolkit where we are able to fix bugs ourselves and a stable roadmap with a long lifespan are the pillars for long-lasting projects.&lt;/p&gt;
&lt;p&gt;JavaFX is a proven solution for this requirement. Java developers have extensive know-how with patterns that enable them to write long-lasting software with a lifespan of 20 years or more.&lt;/p&gt;
&lt;p&gt;The people behind such technologies and how they deal with them are much more important than the technology itself. Ultimately, it&amp;#8217;s all based on dedicated people keeping the technology alive and solid financial support from the companies. The JFX Adopters Meeting has created long-lasting friendships and a solid community.&lt;/p&gt;
&lt;p&gt;And I ask myself: Who will you bring along to the next JFX Adopters Meeting?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://x.com/wolflook/status/1765356417760760283"&gt;Wolfgang Weigend&lt;/a&gt; (ORACLE Global Services Germany GmbH, Master Principal Solution Engineer):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;JavaFX has demonstrated its reliability and stability in mission-critical Java desktop applications for more than 10 years.&lt;/p&gt;
&lt;p&gt;The Java SE Support Roadmap applies to JavaFX technology, taking into account &amp;#8220;Web Deployment Technology and JavaFX&amp;#8221; for earlier versions. Current JavaFX releases can be found &lt;a href="https://jdk.java.net/javafx22/"&gt;on the Java homepage&lt;/a&gt;, as well as the JavaFX 23 early access builds.&lt;/p&gt;
&lt;p&gt;Remarkable is the seamless programming of graphical user interfaces with a single language.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Photo: ZEISS" src="/blog/resources/JFX-adopters-meeting-2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The next JFX Adopters Meeting is planned for fall 2025, ideally the scope should be further enlarged. I will certainly be there &amp;ndash; please include nice quiz interludes again like this year. 😉&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>Performance-Comparison of VMs and Docker containers</title><link href="https://www.qftest.com/en/blog/article/performance-comparison-of-vms-and-docker-containers.html" rel="alternate"/><published>2024-01-15T00:00:00+01:00</published><updated>2024-01-15T00:00:00+01:00</updated><author><name>Marcel Schmied</name></author><id>tag:www.qftest.com,2024-01-15:/en/blog/article/performance-comparison-of-vms-and-docker-containers.html</id><summary type="html">&lt;p&gt;A scientific analysis of the execution speed of QF-Test tests clearly shows that dockerised environments with headless browsers have the advantage. Our experiments show performance gains of up to 300% compared to a conventional VM.&lt;/p&gt;</summary><content type="html">&lt;p&gt;As part of my studies in computer science at the Staatliche Berufsakademie Leipzig together with my &amp;#8220;Praxispartner&amp;#8221; Quality First Software, I wrote a scientific thesis on the topic of &amp;#8220;Testing user interfaces in virtualised environments&amp;#8221;. The thesis deals with the performance analysis of virtual machines and Docker containers for web tests.&lt;/p&gt;
&lt;p&gt;My focus was on comparing the runtimes of tests in QF-Test in different test environments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="/blog/resources/Schmied_Testen_von_Benutzeroberflächen.pdf"&gt;Schmied, Marcel: Testen von Benutzeroberflächen in virtualisierten Umgebungen (PDF in german)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Test results&lt;/h2&gt;
&lt;p&gt;The results have confirmed the assumptions that the Docker container is faster overall than the virtual machine, and that &lt;em&gt;Headless Chrome&lt;/em&gt; is faster than the interactive Chrome with UI.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram &amp;quot;Fast&amp;quot; running nodes" src="/blog/resources/schmied-performance-fast-nodes.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The first diagram shows that QF-Test executes test steps with low overhead more than twice as fast in a dockerised environment. The performance differences are even more pronounced in interactive sessions than in &amp;#8220;headless&amp;#8221; browsers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram Test runs" src="/blog/resources/schmied-performance-test-runs.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The second diagram shows that the measured QF-Test test runs in a dockerised environment are about twice as fast as in a full VM.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram &amp;quot;Long&amp;quot; running nodes" src="/blog/resources/schmied-performance-long-running-nodes.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The third diagram shows that the more time-consuming QF-Test nodes &amp;#8220;DocumentWaiter&amp;#8221;, &amp;#8220;InstallCWRStep&amp;#8221; and &amp;#8220;BrowserClientStarter&amp;#8221; are executed significantly faster in a dockerised environment than in a full VM. However, the differences are not always as significant as for the simpler nodes.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Workflow&lt;/h2&gt;
&lt;p&gt;A colleague from QFS was at my side during the entire time I was working on the project, supporting me with any questions I had and helping me to find topics, structure and organise the project.&lt;/p&gt;
&lt;p&gt;In order to be able to compare the two test environments virtual machine and container environment, I first did extensive research on both, as well as on the topics of test automation and web testing. The detailed results of my research are recorded &lt;a href="/blog/resources/Schmied_Testen_von_Benutzeroberflächen.pdf"&gt;in chapters 2 and 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As an introduction to the practical part of my work, I started by setting up VMs and Docker containers with the same hardware configurations so that I could obtain a reliable test result afterwards. A Linux OS was installed on the VM and then QF-Test. For the Docker container, I only needed to download the &lt;a href="https://hub.docker.com/u/qualityfirstsoftware"&gt;matching Docker image&lt;/a&gt;, which is provided by QFS.&lt;/p&gt;
&lt;p&gt;The Web CarConfigurator (the demo application supplied with QF-Test) was then run several times on each. This resulted in four data sets: A web test with Chrome and a web test with headless Chrome, each for VM and Docker containers. These data sets were later analysed using the methods of statistical result analysis.&lt;/p&gt;
&lt;h2&gt;Conclusion: Save time with Docker and Headless Browser&lt;/h2&gt;
&lt;p&gt;If you look at the individual node execution times, you can see that in some nodes, such as &lt;em&gt;ClientWaiter&lt;/em&gt; or &lt;em&gt;InstallCWRStep&lt;/em&gt;, the differences are relatively small, but in other nodes &amp;ndash; those that interact with the UI &amp;ndash; the differences are greater: This applies to &lt;em&gt;MouseEventStep&lt;/em&gt;, &lt;em&gt;SelectionEventStep&lt;/em&gt; and &lt;em&gt;TextInputStep&lt;/em&gt; with up to 300% performance gain!&lt;/p&gt;
&lt;p&gt;For the fastest possible test execution with the same available resources, you should therefore use a container environment with a headless browser for web tests. This saves up to two thirds of the time required for a test run compared to a virtual machine in combination with a normal interactive browser.&lt;/p&gt;
&lt;p&gt;I personally found it a little more difficult to get started with Docker containers than with VMs. However, it is very easy with the QF-Test containers already prepared by QFS and is definitely worth it when you compare the runtime results.&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>How Webswing is revolutionizing the Migration of Java desktop applications to the web</title><link href="https://www.qftest.com/en/blog/article/how-webswing-is-revolutionizing-the-migration-of-java-desktop-applications-to-the-web.html" rel="alternate"/><published>2024-01-08T00:00:00+01:00</published><updated>2024-01-08T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2024-01-08:/en/blog/article/how-webswing-is-revolutionizing-the-migration-of-java-desktop-applications-to-the-web.html</id><summary type="html">&lt;p&gt;The Webswing Migration Framework is an innovative way to migrate the UI of legacy Java applications to native web components while keeping the underlying business logic unchanged. And QF-Test is along for the ride as the premier Webswing testing solution.&lt;/p&gt;</summary><content type="html">&lt;p&gt;At QFS, we are in contact with &lt;a href="/en/company/references.html"&gt;many different companies using and testing Java desktop applications&lt;/a&gt;. These are often business critical legacy applications. Companies would love to replace these old programs with something more modern and easier to maintain, like a native web app. But completely rewriting old software from scratch takes a lot of time and money. So they stick with what they have.&lt;/p&gt;
&lt;h2&gt;A Java application in a web browser?&lt;/h2&gt;
&lt;p&gt;One company that has totally disrupted this cycle is &lt;a href="https://www.webswing.org/en/"&gt;Webswing&lt;/a&gt;. Webswing is a software solution for migrating existing Java software into the web browser. At its most basic, it’s a web server that mirrors the application’s Java UI into the user’s browser window. This makes it possible to translate an existing Java application into the web with very few steps.&lt;/p&gt;
&lt;p&gt;Because there is a considerable overlap between Webswing customers and potential QF-Test customers, we have built &lt;a href="/en/product/qf-test/java-testing/webswing-testing-with-qf-test.html"&gt;first-class support for testing Webswing applications&lt;/a&gt; into QF-Test. With QF-Test you can test your Java application as it is, you can test your application running through Webswing in the browser as a pure web application, and even both at the same time! That means that QF-Test is perfectly suited to accompany companies (no pun intended) on their migration journey from Java to the web.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test + Webswing = Nahtlose Migrationstests." src="/blog/resources/webswing-qf-test1.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;QF-Test + Webswing = Seamless migration testing.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The Webswing Migration Framework&lt;/h2&gt;
&lt;p&gt;But of course, many companies don’t just want to run their clunky legacy UI in a browser, they want to replace it with something more modern, a native web app.&lt;/p&gt;
&lt;p&gt;That’s why Webswing went one step further and built their &lt;a href="https://www.webswing.org/en/webswing-modernisation-framework"&gt;Webswing Migration Framework&lt;/a&gt;. Companies can use Webswing technology to migrate their Java Application UIs into real web apps piece by piece. Thanks to some ingenious technical wizardry from their developers, Webswing is able to replace individual UI components in an application with real, native Web Components – without even touching the Java application code!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Eine Infografik die zeigt, wie eine Swing App UI Stück für Stück durch native Web-Komponenten ersetzt wird." src="/blog/resources/cSjLWof9pF3BddUeBYABR6G2yiXadvXq4mb38H7e.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;An infographic illustrating how a Swing app UI is replaced piece by piece with native web components until only a tiny bit of Swing UI remains. Source: &lt;a href="https://www.webswing.org/en/webswing-modernisation-framework"&gt;&lt;em&gt;Webswing&lt;/em&gt;&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This means that companies can migrate old Java application UIs to the web at their own pace, and without having to rebuild the entire business logic.&lt;/p&gt;
&lt;p&gt;And you guessed it: QF-Test is along for the ride. Because it can seamlessly interface with both Webswing and web applications, with QF-Test you are able to secure the correct functionality of your application at every step of the migration process.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/blog/resources/webswing-migration-framework-thumb.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=rBHBlICJMcU"&gt;&lt;em&gt;Watch a YouTube video in which Webswing explains the approach of the Webswing Migration Framework.&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>How mgm Advances Test Automation with QF-Test</title><link href="https://www.qftest.com/en/blog/article/how-mgm-advances-test-automation-with-qf-test.html" rel="alternate"/><published>2023-11-02T00:00:00+01:00</published><updated>2023-11-02T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2023-11-02:/en/blog/article/how-mgm-advances-test-automation-with-qf-test.html</id><summary type="html">&lt;p&gt;Our parent company mgm technology partners has published a blog post about how they have been using QF-Test to supercharge their test automation efforts over the last 10 years.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Our parent company mgm technology partners has &lt;a href="https://insights.mgm-tp.com/en/2023/a12-en/how-mgm-advances-test-automation-with-qf-test/"&gt;published a blog post&lt;/a&gt; about how they have been using QF-Test to supercharge their test automation efforts over the last 10 years:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;#8220;At mgm, QF-Test is employed in projects with test automation in a modular and structured manner. Automation of software testing is an integral part of software development, including quality assurance tests like QF-Test. […] We’ve achieved efficiency in our processes by adopting QF-Test on a broad scale.&amp;#8221;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The company utilized QF-Test in a clever way to automate testing of their model-driven Enterprise Low Code Framework &lt;a href="https://www.mgm-tp.com/a12.html"&gt;A12&lt;/a&gt; by building &amp;#8220;mgm A12-ATA&amp;#8221;, a tool that automatically creates end-to-end test cases from A12 model definitions in QF-Test:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;#8220;ATA generates the code from the A12 models for QF-Test automation of the A12 forms, including scopes and SmartIDs. Moreover, it features an interpreter with a library that can control all A12 widgets on the user interface using QF-Test. With ATA, automatic filling or testing of A12 forms with minimal QF-Test implementation is feasible.&amp;#8221;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can read the whole post over on the &lt;a href="https://insights.mgm-tp.com/en/2023/a12-en/how-mgm-advances-test-automation-with-qf-test/"&gt;mgm insights blog&lt;/a&gt;. And to learn more about the powerful tools for testing web applications in QF-Test, you can take a look at &lt;a href="/en/product/qf-test/web-testing.html"&gt;our product page&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>Revolutionizing Test Automation with AI: Insights from TACON 2023</title><link href="https://www.qftest.com/en/blog/article/revolutionizing-test-automation-with-ai-insights-from-tacon-2023.html" rel="alternate"/><published>2023-10-16T00:00:00+02:00</published><updated>2023-10-16T00:00:00+02:00</updated><author><name>Lilia Gargouri</name></author><id>tag:www.qftest.com,2023-10-16:/en/blog/article/revolutionizing-test-automation-with-ai-insights-from-tacon-2023.html</id><summary type="html">&lt;p&gt;The Test Automation Conference (TACON) 2023 was an exploration of AI&amp;#8217;s transformative potential in the domain of software test automation.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://events.summit-community.de/"&gt;&lt;strong&gt;T&lt;/strong&gt;est &lt;strong&gt;A&lt;/strong&gt;utomation &lt;strong&gt;Con&lt;/strong&gt;ference (TACON) 2023&lt;/a&gt;, hosted in Leipzig, Germany on September 26 and 27, was an exploration of AI’s transformative potential in the domain of software test automation. My attendance at TACON was driven by three core objectives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;to comprehend the extent of AI adoption in other organizations,&lt;/li&gt;
&lt;li&gt;to assess our mgm standing in test automation, and&lt;/li&gt;
&lt;li&gt;to foster connections with industry peers and test automation experts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="The TACON 2023 introduction" src="/blog/resources/tacon-2023-keynote.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The TACON 2023 introduction&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;The role of AI in test case creation&lt;/h3&gt;
&lt;p&gt;One of the prominent topics discussed at TACON was AI’s role in test case creation.&lt;/p&gt;
&lt;p&gt;Imagine AI generating test cases based on ticket descriptions or User Stories. The potential is undeniable, but challenges emerge. AI can produce an avalanche of test cases, often more than a human tester can handle. The critical questions arise: Who will ensure the quality of these generated test cases? Who will condense them into manageable sets? And, most importantly, who will execute and maintain this vast set of test cases?&lt;/p&gt;
&lt;h3&gt;AI for defect prediction&lt;/h3&gt;
&lt;p&gt;Another fascinating application is AI’s ability to predict defects, helping to mitigate potential issues before they reach production. By analyzing historical test data and code changes, AI becomes a proactive guardian, alerting development teams to potential pitfalls and enabling them to take corrective action early in the development cycle.&lt;/p&gt;
&lt;h3&gt;AI as a test result evaluator&lt;/h3&gt;
&lt;p&gt;Furthermore, AI assists in error analysis by swiftly identifying the source of issues through the analysis of test results and log files, enabling rapid and effective remediation.&lt;/p&gt;
&lt;h3&gt;Gravity by Smartesting&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.smartesting.com/en/"&gt;Gravity&lt;/a&gt; by Smartesting garnered attention for its innovative approach to test automation, leveraging AI-driven insights.&lt;/p&gt;
&lt;p&gt;By tracking user activities in production and generating reports on the most visited actions, workflows, and pages, Gravity empowers QA teams to enhance test coverage effectively. It even generates test automation code on demand, making end-to-end tests more realistic and easy.&lt;/p&gt;
&lt;h3&gt;Sustainability in software development&lt;/h3&gt;
&lt;p&gt;Lastly, TACON reminded us of the pressing need for sustainability in software development. The digital footprint of our industry is undeniable, contributing to environmental challenges. Embracing the triple bottom line approach—evaluating software development from social, economic, and ecological perspectives—becomes a necessity.&lt;/p&gt;
&lt;p&gt;Tools like the &lt;a href="https://www.digitalcarbonfootprint.eu/"&gt;Digital Carbon Footprint Calculator&lt;/a&gt; are essential in measuring and reducing our ecological impact.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tacon 2023 Footprint" src="/blog/resources/tacon-2023-footprint.png" title="Tacon 2023 Footprint" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Explore your carbon footprint using digitalcarbonfootprint.eu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tacon 2023 Fonts" src="/blog/resources/tacon-2023-fonts.png" title="Tacon 2023 Fonts" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The fonts table shocked me! (Source: bit.ly/fontsImpactOnCarbonFootprint)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;mgm’s position in comparison&lt;/h3&gt;
&lt;p&gt;Our &lt;a href="https://www.mgm-tp.com/a12.html"&gt;mgm A12 low-code platform&lt;/a&gt; and associated A12 test automation catalog (QF-Test, Smart ID, Resolver, ATA, TDG) are and remain outstanding. The mgm model-driven Quality tools (&lt;a href="https://insights.mgm-tp.com/en/2023/a12-en/mgm-a12-test-data-generator-automatic-test-data-generation/"&gt;A12 Test Data Generator&lt;/a&gt;, &lt;a href="https://insights.mgm-tp.com/en/2023/testing/deepdive-a12-end-to-end-test-automation/"&gt;A12 Automated Test Automation&lt;/a&gt;, A12 Test Data Suite) distinguish us from other companies and make us very special and also very efficient!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.qftest.com/doc/manual/en/bp_componentrecognition.html"&gt;The QF-Test approach to component detection&lt;/a&gt;, based on a logical UI derived from the physical UI, remains exceptional and contributes to very stable component detection.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;TACON 2023 offered a glimpse into the future of test automation with AI at its core. From test case creation to predictive defect prevention, AI is reshaping the landscape.&lt;/p&gt;
&lt;p&gt;As we are heading for AI transformation, it’s imperative that we do so responsibly, considering the environmental impact of our software development efforts. Let’s harness the power of AI while promoting sustainability for a better, more efficient future of software development and testing.&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>Mitigation of the WebP 0-day vulnerability CVE-2023-4863 in QF-Test</title><link href="https://www.qftest.com/en/blog/article/mitigation-of-the-webp-0-day-vulnerability-cve-2023-4863-in-qf-test.html" rel="alternate"/><published>2023-09-28T00:00:00+02:00</published><updated>2023-09-28T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2023-09-28:/en/blog/article/mitigation-of-the-webp-0-day-vulnerability-cve-2023-4863-in-qf-test.html</id><summary type="html">&lt;p&gt;Google has disclosed a critical vulnerability in an image library that is also included in some versions of QF-Test. Here are steps you can take to protect yourself.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Last updated 10/11/2023, 10:30 AM.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We are aware of the recently disclosed &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-4863"&gt;critical vulnerability in the &lt;code&gt;libwebp&lt;/code&gt; library (CVE-2023-4863)&lt;/a&gt;, potentially enabling remote code execution through a specially crafted WebP image file.&lt;/p&gt;
&lt;p&gt;If QF-Test is used for opening files from untrusted sources, QF-Test versions from 4.5.0 to (including) 7.0.5 are vulnerable to this exploit through maliciously modified run logs or test suites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Today we released QF-Test 7.0.6 which fixes this vulnerability. We advise all our users to update to the latest version.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are unable to update to QF-Test 7.0.6 &lt;em&gt;and&lt;/em&gt; need to open untrusted run logs or test suites with QF-Test 7.0.5 or older, you can secure that installation of QF-Test against this vulnerability with the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the QF-Test system directory of the QF-Test installation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To do this, start QF-Test, select &amp;#8220;Help&amp;#8221; – &amp;#8220;About&amp;#8221; from the QF-Test menu bar (on macOS &amp;#8220;QF-Test&amp;#8221; – &amp;#8220;About QF-Test&amp;#8221;), switch to the &amp;#8220;System Info&amp;#8221; tab and click the link next to &lt;code&gt;dir.version&lt;/code&gt;.
2. Quit all running instances of QF-Test.
3. Navigate to the subdirectory &lt;code&gt;bin&lt;/code&gt; of the QF-Test system directory.
4. Delete the directory &lt;code&gt;webp&lt;/code&gt; from the &lt;code&gt;bin&lt;/code&gt; subdirectory.
5. Download the updated WebP library and extract the included &lt;code&gt;webp&lt;/code&gt; directory: &lt;a href="/blog/resources/webp-1.3.2.zip"&gt;Updated WebP library&lt;/a&gt;.
6. Copy the extracted &lt;code&gt;webp&lt;/code&gt; directory to the &lt;code&gt;bin&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You may need administrator privileges to perform this update.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 10/11/2023:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the meantime, the embedded Chrome browser for QF-Driver on Windows has also been updated with QF-Test 7.0.7.&lt;/p&gt;
&lt;p&gt;Besides, the Electron demos have been updated. These are downloaded automatically by the Electron demo test suites. If you want to be on the safe side, delete possibly existing old demos from the directory &lt;em&gt;electron&lt;/em&gt; in the cache directory of QF-Test. This can be found similar to point 1 via the link &lt;code&gt;dir.cache&lt;/code&gt;.&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Annotating screenshots with QF-Test</title><link href="https://www.qftest.com/en/blog/article/annotating-screenshots-with-qf-test.html" rel="alternate"/><published>2023-08-14T00:00:00+02:00</published><updated>2023-08-14T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2023-08-14:/en/blog/article/annotating-screenshots-with-qf-test.html</id><summary type="html">&lt;p&gt;For many people a picture is worth a thousand words. That&amp;#8217;s why, in this article, we will show how to add various annotations to screenshots using QF-Test.&lt;/p&gt;</summary><content type="html">&lt;p&gt;It is well known that, for many people, a picture is worth a thousand words. Accordingly, screenshots are an important part of QF-Test.&lt;/p&gt;
&lt;p&gt;In my last &lt;a href="/en/blog/article/creating-screenshots-with-qf-test.html"&gt;blog article &amp;#8220;Creating Screenshots with QF-Test&amp;#8221;&lt;/a&gt; I went into how exactly to create screenshots manually.&lt;/p&gt;
&lt;p&gt;Today I would like to demonstrate how to edit such images with QF-Test, for example to insert simple geometric figures. This can be very useful to highlight certain regions in the image – for example a click position or a certain component.&lt;/p&gt;
&lt;p&gt;Let’s start by getting an image of a window component in a Jython SUT script:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;imagewrapper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;

&lt;span class="n"&gt;iw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#Window:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grabImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we get the necessary data for image processing, namely the width of the image and the &lt;em&gt;ARGB array&lt;/em&gt; to be processed:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;width = img.getWidth()
argb = img.getARGB()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;ARGB array&lt;/em&gt; contains the color values of all pixels of the image, line by line, starting from the upper left corner.&lt;/p&gt;
&lt;p&gt;For example, to address the fourth pixel in the third row of the image, the value with index &lt;code&gt;4 + 3 * width&lt;/code&gt; must be addressed in the array.&lt;/p&gt;
&lt;p&gt;The color value first specifies the alpha value, then the red value, the green value and finally the blue value of the pixel, where each value can be anything between 0 and 255.&lt;/p&gt;
&lt;p&gt;So now we can modify the array to add a 15 × 15 pixel red cross at any position. The following code can be used to do this. For this example, it is important to maintain at least a 15 pixel gap from the edge of the image, otherwise the cross will appear messed-up.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QF&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;argb&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFF0000&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;argb&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFF0000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, we need to pass this array back to the image object and then save the image object in the run log, for example, to make it available to another user:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;img.setARGB(argb)
rc.logImage(img)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once everything is said and done, we get the following complete script:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;imagewrapper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;

&lt;span class="n"&gt;iw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#Window:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grabImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;argb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getARGB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Get coordinates from QF-Test variables&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;argb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFF0000&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;argb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xFFF0000&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setARGB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="" src="/blog/resources/2023-08-image-processing.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;#8220;X&amp;#8221; marks the spot.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the same way you can also draw circles, rectangles or any number of similar geometric figures on the drawing surface using the corresponding mathematical functions.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>I received a license upgrade. What now?</title><link href="https://www.qftest.com/en/blog/article/i-received-a-license-upgrade-what-now.html" rel="alternate"/><published>2023-06-12T00:00:00+02:00</published><updated>2023-06-12T00:00:00+02:00</updated><author><name>Mike Schmidt</name></author><id>tag:www.qftest.com,2023-06-12:/en/blog/article/i-received-a-license-upgrade-what-now.html</id><summary type="html">&lt;p&gt;I have a file called &amp;#8220;license.new&amp;#8221;. Why am I getting the error message &amp;#8220;Invalid license&amp;#8221;?&lt;/p&gt;</summary><content type="html">&lt;h2&gt;I have a file called &amp;#8220;license.new&amp;#8221;. Why am I getting the error message &amp;#8220;Invalid license&amp;#8221;?&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Screenshot of a QF-Test error message" src="/blog/resources/license-new-error-en.png" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The license file &amp;#8216;license.new&amp;#8217; for company YourCompany LLC is invalid.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If this problem occurs after a license update, please contact your local license administrator.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you see this message on your screen, you have a license update file instead of a regular license file. You use this file (ideally once) in combination with a regular license file to create a new one. However, your computer does not have the previous QF-Test license to which the update could be applied.&lt;/p&gt;
&lt;p&gt;The following instructions are therefore not so much intended for you as for the person responsible for distributing QF-Test licenses at your company.&lt;/p&gt;
&lt;h2&gt;I have received a maintenance upgrade from QFS. Should I just forward this email to the affected colleagues?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;No&lt;/strong&gt;, please don’t.&lt;/p&gt;
&lt;p&gt;Instead, forward it only to the person responsible for distributing QF-Test licenses at your company. If you are that person, this blog post will help you.&lt;/p&gt;
&lt;h2&gt;Okay, so how do I get to the actual license file?&lt;/h2&gt;
&lt;p&gt;Well, the recipient of the maintenance upgrade will also receive the following information in their email:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can activate or update licenses directly within QF-Test via &amp;#8220;Help&amp;#8221; &amp;gt; &amp;#8220;Update license…&amp;#8221;.  &lt;/p&gt;
&lt;p&gt;Please distribute the resulting new license file to the affected workstations afterwards and import it into your backup system.&lt;/p&gt;
&lt;p&gt;You can find more information &lt;a href="https://www.qftest.com/doc/manual/en/user_installation.html#usec_licensefile"&gt;in the manual&lt;/a&gt; or &lt;a href="https://www.youtube.com/watch?v=pm5lhuGD5UQ&amp;amp;t=248s"&gt;in this YouTube video&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Therefore, the recipient’s next steps should be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;perform the license update process as described in the email&lt;/li&gt;
&lt;li&gt;save the new license file thus generated locally&lt;/li&gt;
&lt;li&gt;and then distribute it to the affected workstations&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Backing up the locally generated license file&lt;/h3&gt;
&lt;p&gt;We have explained the dialog that can be accessed via &amp;#8220;Help&amp;#8221; &amp;gt; &amp;#8220;Info…&amp;#8221; in QF-Test in our previous blog post &lt;a href="/en/blog/article/license-info-explained.html"&gt;QF-Test license information explained&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At the bottom of said dialog you will not only find the specific location of the license file, but you can also navigate there directly by clicking on the file path.&lt;/p&gt;
&lt;h3&gt;Distributing the license&lt;/h3&gt;
&lt;p&gt;Every company has different mechanisms for distributing software (including associated configuration and license files). For this reason, it is difficult for us to describe the appropriate process in your environment. Your administrators would be the better contacts.&lt;/p&gt;
&lt;p&gt;One possibility would be to use a file and/or folder synchronizing software. Or even storing them on a central file server in conjunction with &lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html#arg_license"&gt;the QF-Test parameter &lt;code&gt;-license&lt;/code&gt;&lt;/a&gt; along with the appropriate path. For Windows domains, it could also be distributed via a Group Policy Object (GPO).&lt;/p&gt;
&lt;p&gt;As you can see, there are any number of ways to implement this. We recommend to use the same mechanism you already use to distribute QF-Test installations and their configuration.&lt;/p&gt;
&lt;h2&gt;Okay, but why do &amp;#8220;license.new&amp;#8221; files and this update process exist at all?&lt;/h2&gt;
&lt;p&gt;Well, customers who use QF-Test in more than one department or location usually have one central purchasing department, but multiple license files in use. However, that department does not necessarily know which of the various license files to distribute to which locations. In the past, this has resulted in the customer ultimately using only one license file in multiple physical locations. Apart from the fact that this was against licensing conditions, the customer no longer had their full license feature set at their disposal.&lt;/p&gt;
&lt;p&gt;Hence the &amp;#8220;license.new&amp;#8221; files. Because they only ever fit one license file, the purchasing department can simply distribute all license updates to all locations without having to worry about which ones belong where.&lt;/p&gt;</content><category term="blog"/><category term="license"/></entry><entry><title>Integrating QF-Test with TeamCity in three easy steps</title><link href="https://www.qftest.com/en/blog/article/qftest-teamcity-ci-integration.html" rel="alternate"/><published>2023-05-11T00:00:00+02:00</published><updated>2023-05-11T00:00:00+02:00</updated><author><name>Karlheinz Kellerer</name></author><id>tag:www.qftest.com,2023-05-11:/en/blog/article/qftest-teamcity-ci-integration.html</id><summary type="html">&lt;p&gt;QF-Test can be integrated with common CI tools like Jenkins and TeamCity. In this post, we demonstrate how to integrate QF-Test into TeamCity CI in three easy steps&lt;/p&gt;</summary><content type="html">&lt;p&gt;QF-Test can be easily integrated with common CI tools like Jenkins and TeamCity.&lt;br /&gt;
 In fact, a &lt;a href="https://www.qftest.com/doc/manual/en/user_jenkins.html"&gt;QF-Test plug-in for Jenkins&lt;/a&gt; is available out-of-the-box which makes things pretty easy but even with other CI tools, an integration is possible with just a few steps.&lt;/p&gt;
&lt;p&gt;In this blog post, I will demonstrate these steps for &lt;a href="https://www.jetbrains.com/teamcity/"&gt;TeamCity CI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Important note: CI tools often suggest using a headless service for the agent process. However, for UI testing it is crucial to use a real user session to ensure that the desktop environment behaves as expected. This may not be the case if a CI agent is running as a service. That’s why we recommend starting the agent in a real user session. Refer to the &lt;a href="https://www.qftest.com/doc/manual/en/buildtools.html"&gt;Integration with Development Tools&lt;/a&gt; chapter of our user manual for additional information.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Step 1: Command Line Execution Build Step&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Note: You need to have a project with a build configuration ready to go.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Central element for integrating QF-Test with TeamCity is the &amp;#8220;execution&amp;#8221; build step. QF-Test provides an extensive command line interface which is documented in the &lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html"&gt;reference manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In TeamCity, add a new build step …&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;Build steps&amp;quot; page" src="/blog/resources/teamcity-integration-step1-1.png" /&gt;&lt;/p&gt;
&lt;p&gt;… and select a &amp;#8220;Command Line&amp;#8221; build step.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;New build step&amp;quot; page" src="/blog/resources/teamcity-integration-step1-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;There,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;define an appropriate step name&lt;/li&gt;
&lt;li&gt;select &amp;#8220;Executable with parameter&amp;#8221;&lt;/li&gt;
&lt;li&gt;set &amp;#8220;qftestc.exe&amp;#8221; as &amp;#8220;Command executable&amp;#8221; (qftestc.exe is a version of qftest.exe which is optimized for command line usage)&lt;/li&gt;
&lt;li&gt;define the following parameters:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-batch -report.html report
-report.junit junitreport
-exitcode-ignore-exception -exitcode-ignore-error -exitcode-ignore-warning
-nomessagewindow
&amp;quot;&amp;lt;Full path to the test suite to be executed&amp;gt;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;Build step: Command line&amp;quot; page" src="/blog/resources/teamcity-integration-step1-3.png" /&gt;&lt;/p&gt;
&lt;p&gt;Here is a quick explanation of the used command line parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-batch&lt;/code&gt;: we want to run QF-Test in batch mode without QF-Test UI&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-runlogdir runlog&lt;/code&gt;: run logs should be put into a dedicated sub-directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-report.html report&lt;/code&gt;: we want to create an HTML report which we will integrate into TeamCity later&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-report.junit junitreport&lt;/code&gt;: an additional report in JUnit format allows TeamCity to parse the test results properly&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-exitcode-ignore-exception -exitcode-ignore-error -exitcode-ignore-warning&lt;/code&gt;: don’t return a non-zero exit code on test errors, since they would be interpreted as hard crashes by TeamCity&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-nomessagewindow&lt;/code&gt;: Don’t display any error message windows, print everything to standard output instead&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, now we have completed the first step and can start a first test run before creating the next configuration.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: You need to have at least one TeamCity agent available. As a prerequisite, QF-Test must be installed on the host of the agent.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;Build steps&amp;quot; page. The &amp;quot;Run&amp;quot; button in the top right is highlighted" src="/blog/resources/teamcity-integration-step1-4.png" /&gt;&lt;/p&gt;
&lt;p&gt;We can switch to the &amp;#8220;build configuration page&amp;#8221; to see the build result.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the TeamCity &amp;quot;Go to build configuration page&amp;quot; button" src="/blog/resources/teamcity-integration-step1-5.png" /&gt;&lt;/p&gt;
&lt;p&gt;Here we should see the results of our first test run, hopefully with status &amp;#8220;Success&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;MyBuild&amp;quot; page. The status &amp;quot;success&amp;quot; is highlighted" src="/blog/resources/teamcity-integration-step1-6.png" /&gt;&lt;/p&gt;
&lt;p&gt;That’s great but &amp;#8220;Success&amp;#8221; just means that the QF-Test process has returned a &lt;code&gt;0&lt;/code&gt; exit code on the command line, but there is no information about actually successful of failed test cases.&lt;/p&gt;
&lt;h2&gt;Step 2: Test result reporting&lt;/h2&gt;
&lt;p&gt;To achieve this, we will use the JUnit reports which have been generated by QF-Test. Those contain the information about successful and failed test cases that can be interpreted by TeamCity.&lt;/p&gt;
&lt;p&gt;Within TeamCity we need to&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;switch to the &amp;#8220;Build Features&amp;#8221; area&lt;/li&gt;
&lt;li&gt;add a new build feature&lt;/li&gt;
&lt;li&gt;choose the &amp;#8220;XML report processing&amp;#8221; with report type &amp;#8220;Ant Junit&amp;#8221;&lt;/li&gt;
&lt;li&gt;create a Monitoring rule &lt;code&gt;%teamcity.build.workingDir%/junitreport/report_junit.xml&lt;/code&gt;, which is where the JUnit report is created by QF-Test as defined in the command line build step&lt;/li&gt;
&lt;li&gt;check &amp;#8220;Verbose output&amp;#8221; (optional)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Screenshot of how to add a Build Feature in TeamCity" src="/blog/resources/teamcity-integration-step2-1.png" /&gt;&lt;/p&gt;
&lt;p&gt;Saving and then triggering another run on the build configuration page gets us a much more detailed result. Notice that there was one passed and one failed test in our run, which is expected for the sample suite I used.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;MyBuild&amp;quot; page. The new status &amp;quot;Tests failed&amp;quot; is highlighted" src="/blog/resources/teamcity-integration-step2-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;So far, so good. But one important feature is still missing. For test result analysis QF-Test provides run logs that should be available from within TeamCity, as well as the QF-Test HTML reports that serve as a great overview.&lt;/p&gt;
&lt;h2&gt;Step 3: Run log and HTML Custom Report Integration&lt;/h2&gt;
&lt;p&gt;First, both the run logs and HTML report need to be defined as build artifacts so they are stored by TeamCity.&lt;br /&gt;
 This can be done in the &amp;#8220;General Settings&amp;#8221; area:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;report =&amp;gt; report.zip&lt;/code&gt;: The report directory will be zipped up and stored&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runlog =&amp;gt; runlog&lt;/code&gt; The &lt;code&gt;runlog&lt;/code&gt; directory will also get stored. Since &lt;code&gt;.qrz&lt;/code&gt; run logs are already compressed, an additional zip step would not make sense.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Reminder: Run logs provide detailed information about the test run. HTML reports provide an overview.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the TeamCity &amp;quot;MyBuild&amp;quot; general settings page" src="/blog/resources/teamcity-integration-step3-1.png" /&gt;&lt;/p&gt;
&lt;p&gt;Triggering another run will deliver us the respective artifacts, containing the &lt;code&gt;runlog&lt;/code&gt; directory and &lt;code&gt;report.zip&lt;/code&gt;, ready for downloading.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;MyBuild&amp;quot; page highlighting where to download an artifact for a specificbuild" src="/blog/resources/teamcity-integration-step3-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;Now, only one final integration detail remains: displaying the QF-Test HTML report in a separate build tab.&lt;/p&gt;
&lt;p&gt;To do this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;navigate to the administration page of your project&lt;/li&gt;
&lt;li&gt;and from there to the &amp;#8220;Report Tabs&amp;#8221; page&lt;/li&gt;
&lt;li&gt;to &amp;#8220;Create a new build report tab&amp;#8221;&lt;/li&gt;
&lt;li&gt;with the title &amp;#8220;QF-Test Report&amp;#8221;&lt;/li&gt;
&lt;li&gt;and a &amp;#8220;Start page&amp;#8221; of &lt;code&gt;report.zip!report.html&lt;/code&gt;, which leads to the &lt;code&gt;report.html&lt;/code&gt; page inside the &lt;code&gt;report.zip&lt;/code&gt; build artifact.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;Edit Report Tab Settings&amp;quot; popover" src="/blog/resources/teamcity-integration-step3-3.png" /&gt;&lt;/p&gt;
&lt;p&gt;After heading back to the project and running another build, open the build details by clicking the link below &amp;#8220;Status&amp;#8221; …&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of TeamCity &amp;quot;MyBuild&amp;quot; page. The hyperlink on the status text &amp;quot;Tests failed&amp;quot; is highlighted" src="/blog/resources/teamcity-integration-step3-4.png" /&gt;&lt;/p&gt;
&lt;p&gt;… and then the familiar QF-Test HTML report can be found right inside the new &amp;#8220;QF-Test Report&amp;#8221; tab.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a QF-Test HTML report embedded in a separate TeamCity tab" src="/blog/resources/teamcity-integration-step3-5.png" /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This is just a first taste of the integrations possible between TeamCity and QF-Test.&lt;/p&gt;
&lt;p&gt;You may want to take a look at the other &lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html"&gt;QF-Test command line options documented in our reference manual&lt;/a&gt;. Especially the parameter &lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html#arg_suitesfile"&gt;&lt;code&gt;-suitesfile &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/a&gt; can be handy for customizing the test suites from outside the build step. Also, the &lt;a href="https://www.jetbrains.com/help/teamcity/integrating-teamcity-with-other-tools.html"&gt;TeamCity documentation&lt;/a&gt; includes even more advanced possibilities for integration, such as the concept of &lt;a href="https://www.jetbrains.com/help/teamcity/service-messages.html#Reporting+Tests"&gt;Service Messages&lt;/a&gt;, which communicate progress via standard output during test execution.&lt;/p&gt;
&lt;p&gt;In case you have additional suggestions or questions about integrating QF-Test with TeamCity CI, feel free to let us know by &lt;a href="/en/support/product-support.html"&gt;contacting our support team&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Switching languages in QF-Test</title><link href="https://www.qftest.com/en/blog/article/switching-languages-in-qf-test.html" rel="alternate"/><published>2023-02-07T00:00:00+01:00</published><updated>2023-02-07T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2023-02-07:/en/blog/article/switching-languages-in-qf-test.html</id><summary type="html">&lt;p&gt;QF-Test is multi-lingual &amp;ndash; and many of our users are, too. In this post we explain how you can control the interface language of QF-Test.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Did you know that QF-Test is not only available in English, but is also completely localized into German?&lt;/p&gt;
&lt;p&gt;Admittedly, setting the language of the QF-Test interface is something most of our users never have to care about, since the system setting is used by default.&lt;/p&gt;
&lt;p&gt;However, it does happen that you have to change the language of QF-Test for a presentation, for example.&lt;/p&gt;
&lt;p&gt;Thats why I want to take the opportunity to explain exactly all of the ways you can set the QF-Test interface language on the different platforms supported by QF-Test.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Usually, you don’t have to worry about setting the QF-Test interface language. QF-Test will use the same language as your operating system, and will fall back to English. You can switch your OS interface language, log out and back in, and QF-Test will appear in the matching language.&lt;/p&gt;
&lt;p&gt;However, for customers who need just a little more control, there are additional methods for adjusting this behavior.&lt;/p&gt;
&lt;h2&gt;On Windows&lt;/h2&gt;
&lt;p&gt;On Windows, QF-Test comes bundled with the configuration utility &lt;code&gt;qfconfig.exe&lt;/code&gt;. You can launch this tool from the same directory as your &lt;code&gt;qftest.exe&lt;/code&gt; executable, or just search for &amp;#8220;qfconfig&amp;#8221; in the Start menu.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;img alt="Screenshot of the QF-Test Java Configuration utility." src="/blog/resources/qftest-java-configuration-en.png" /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot of the QF-Test Java Configuration utility.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here, you can override the default &amp;#8220;Language&amp;#8221; setting with the language of your choice.&lt;/p&gt;
&lt;h2&gt;On Linux&lt;/h2&gt;
&lt;p&gt;On other platforms, the configuration utility is not available. On Linux, the preferred method of switching QF-Test language is by launching with command line parameters for the &lt;code&gt;qftest&lt;/code&gt; start script:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qftest -J-Duser.language=de&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This parameter may seem familiar. Because QF-Test is a Java application, it uses the Java APIs for managing its interface language. The language of the JVM is controlled with the command line parameter &lt;code&gt;-Duser.language&lt;/code&gt;. By convention, to pass a command line parameter from a Java-based application to the JVM, the prefix &lt;code&gt;-J&lt;/code&gt; is used, resulting in &lt;code&gt;-J-Duser.language&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This method of course also works on other platforms.&lt;/p&gt;
&lt;p&gt;You can learn more about all of the available parameters in the QF-Test manual chapter &lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html#sec_commandline"&gt;Command line arguments&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;On macOS&lt;/h2&gt;
&lt;p&gt;On computers running macOS, starting QF-Test with command line parameters is relatively cumbersome, because the actual binary is hidden within the &amp;#8220;QF-Test.app&amp;#8221; application bundle.&lt;/p&gt;
&lt;p&gt;Thats why, in QF-Test for macOS, we have added an additional section in the QF-Test application settings at &amp;#8220;General&amp;#8221; → &amp;#8220;Startup&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;img alt="Screenshot of the QF-Test settings window section &amp;quot;Startup&amp;quot;." src="/blog/resources/qftest-macos-startup-settings-en.png" /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot of the QF-Test settings window section &amp;#8220;Startup&amp;#8221;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Simply paste the following into &amp;#8220;Startup Arguments&amp;#8221; to always use the German localization:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-J-Duser.language=de&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Of course, you can set all of the available &lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html#sec_commandline"&gt;command line arguments&lt;/a&gt; here. And if you ever accidentally mess these settings up in a way that QF-Test will no longer launch at all, you can find &lt;a href="https://www.qftest.com/doc/manual/en/user_installation.html#usec_execution"&gt;recovery instructions in the manual&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;And that’s it! These are all the ways to change the interface language of QF-Test across all operating systems.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>SmartID – The next generation of component recognition</title><link href="https://www.qftest.com/en/blog/article/smartid-the-next-generation-of-component-recognition.html" rel="alternate"/><published>2022-12-16T00:00:00+01:00</published><updated>2022-12-16T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2022-12-16:/en/blog/article/smartid-the-next-generation-of-component-recognition.html</id><summary type="html">&lt;p&gt;We are always working on improving component recognition in QF-Test. With QF-Test 6.0 we introduced a completely new paradigm: SmartID&lt;/p&gt;</summary><content type="html">&lt;p&gt;Many QF-Test users are familiar with the problem: As your tests grow, so does the component tree under &amp;#8220;Windows and components&amp;#8221;. It eventually grows so huge that you are forced to give up on keeping on top of individual components.&lt;/p&gt;
&lt;p&gt;For less complex test scenarios, this is not a problem. QF-Test is designed in a way that ideally you don&amp;#8217;t have to pay attention to the component tree at all; thanks to intelligent &amp;#8220;record &amp;amp; replay&amp;#8221;, QF-Test takes care of creating and reusing components automatically.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a QF-Test window with a huge component tree." src="/blog/resources/smartid-componenttree-en.png" /&gt;&lt;/p&gt;
&lt;p&gt;However, more than a few of our customers broke through that limit where maintenance of the component tree could not be left to QF-Test alone. Together with them we worked on different solutions. A frequently used pattern were &lt;strong&gt;generic components&lt;/strong&gt;. QF-Test version 6.0 then introduced the superior &lt;strong&gt;SmartID&lt;/strong&gt; feature.&lt;/p&gt;
&lt;h2&gt;A detour through generic components&lt;/h2&gt;
&lt;p&gt;A generic component is a component where the deciding properties are filled with a variable, so the component can be resolved to different UI elements depending on the current variable value. In the right hands, generic components are a powerful tool to keep the component tree clear and concise, thus increasing overall readability of a test suite.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a QF-Test window with a typical generic component." src="/blog/resources/smartid-genericbutton-en.png" /&gt;&lt;/p&gt;
&lt;p&gt;But generic components repeatedly reached their limits as well. The use of the QF-Test recording function for test development was severely limited. To completely rely on generic components, individual components for every single class and generic property &lt;em&gt;and all combinations of both&lt;/em&gt; had to be created and maintained. This quickly became tedious. It got especially unmanageable when changes in the application under test required modifications to component recognition. Then, variable values and procedure parameters had to be adjusted across all test suites until the generic components were again correctly resolved.&lt;/p&gt;
&lt;p&gt;So, we continued working on this problem, and finally arrived at a completely new paradigm: &lt;strong&gt;SmartID&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;SmartID – Component recognition &amp;#8220;smart&amp;#8221;&lt;/h2&gt;
&lt;p&gt;Different from generic components, SmartID is not just a usage pattern, but a completely new feature for addressing UI elements. During development of SmartID, our guiding principle was to keep simple things simple.&lt;/p&gt;
&lt;p&gt;One of the strenghts of the classic component tree is QF-Test mostly taking care of maintaining the concrete components (thanks to the recording feature). The decicisive advantage of generic components is avoiding the complexity of the component tree by focusing only on relevant element properties.&lt;/p&gt;
&lt;p&gt;SmartID in turn combines these two advantages: Taking care of the component tree is no longer neccessary, and the deciding recognition criteria can still be precisely targeted.&lt;/p&gt;
&lt;p&gt;But how does it work? The component definition is moved entirely into the &amp;#8220;QF-Test component ID&amp;#8221; reference:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a QF-Test window with a mouse event step. The SmartID #Button:OK fills the &amp;quot;QF-Test component ID&amp;quot; input field." src="/blog/resources/smartid-example-en.png" /&gt;&lt;/p&gt;
&lt;p&gt;In a SmartID, one or a combination of recognition criteria is explicitly selected through a &lt;a href="https://www.qftest.com/doc/manual/en/sec_smartid_syntax.html"&gt;powerful syntax&lt;/a&gt;. The SmartID is declared directly in place of the QF-Test ID of the component. So, no component step needs to be referenced or generated at all. And: QF-Test is intelligent enough to even create SmartIDs instead of component steps during recording of clicks and checks, if desired.&lt;/p&gt;
&lt;p&gt;All the while, SmartID has access to all the same recognition criteria as recorded components.&lt;/p&gt;
&lt;h2&gt;Areas of SmartID application&lt;/h2&gt;
&lt;p&gt;Thanks to SmartID it is now possible to comfortably do test-driven development (TDD) or keyword-driven testing (KDT) with QF-Test. SmartIDs do not have to be recorded, but can be derived from the specification of an &lt;em&gt;even not yet developed&lt;/em&gt; application.&lt;/p&gt;
&lt;p&gt;This independence from the concrete state of the SUT also enables easier integration with other test tools like &lt;a href="https://www.qftest.com/doc/manual/en/user_robotframework.html"&gt;Robot Framework&lt;/a&gt;, or technology-agnostic tests, for example of a Web and an Android client of the same application.&lt;/p&gt;
&lt;h2&gt;Available since QF-Test 6.0&lt;/h2&gt;
&lt;p&gt;Since QF-Test version 6.0, SmartID is fully available as a preview feature. Automatic recording can be activated through the &amp;#8220;Recording&amp;#8221; menu.&lt;/p&gt;
&lt;p&gt;But what does &amp;#8220;preview&amp;#8221; mean? We want to allow ourselves to add fixes and small changes in the details of SmartID, because we have not jet implemented our full vision for the feature. However, we encourage our customers to use SmartID without fear of backwards compatibility issues. More than a few companies (ourselves included) already have SmartID in productive use.&lt;/p&gt;
&lt;h2&gt;Is this the end of the component tree?&lt;/h2&gt;
&lt;p&gt;We are conviced that SmartID will be the best way to target UI components in QF-Test going forward. We want to especially discourage the ongoing use of generic components, since SmartID outperforms them in almost all areas of application.&lt;/p&gt;
&lt;p&gt;Still, the component tree in QF-Test will not be deprecated in the forseeable future. There will always be situations where a classic recorded component will be easier to manage or faster than a SmartID. SmartID and classic components can be seamlessly combined, so there is no need to for rivalry.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Do you want to learn more about SmartID? You can read about &lt;a href="https://www.qftest.com/doc/manual/en/user_smartid.html"&gt;the nitty-gritty details in the QF-Test manual&lt;/a&gt; or watch the &lt;a href="/en/support/training-consulting/webinars.html#c17992"&gt;video recording of our special webinar &amp;#8220;SmartID&amp;#8221;&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>My internship at QFS: Sorting and comparing XML files (and other things)</title><link href="https://www.qftest.com/en/blog/article/my-internship-at-qfs-sorting-and-comparing-xml-files-and-other-things.html" rel="alternate"/><published>2022-10-24T00:00:00+02:00</published><updated>2022-10-24T00:00:00+02:00</updated><author><name>Sarah Wiegel</name></author><id>tag:www.qftest.com,2022-10-24:/en/blog/article/my-internship-at-qfs-sorting-and-comparing-xml-files-and-other-things.html</id><summary type="html">&lt;p&gt;How I used SAX, multi-threading, and token-based parsing to develop a (much!) faster XML comparison library.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&amp;#8220;Geodata systems and optimization: Evaluation and optimization of the testability of geodata systems, in particular using QF-Test&amp;#8221; was the topic of my internship semester at QFS. It was part of my studies in &amp;#8220;Geoinformatics and Navigation&amp;#8221; at the University of Applied Sciences Munich and lasted from September 2021 until the beginning of February 2022.&lt;/p&gt;
&lt;p&gt;Geodata and also geoinformation systems have often been part of lectures during my studies and so I had an idea about how complex these applications are. They are used to manage, store and/or display geodata (i.e. information that can somehow be drawn on a map). Often such systems have an interface where various georeferenced geometries (such as estates, roads, or boundaries) are displayed on maps. Since the functionality can be very diverse and far-reaching, the topic of testability is very important and exciting.&lt;/p&gt;
&lt;p&gt;Beyond the internship semester, I have been working as a student trainee at QFS for quite some time and was able to continue and complete my projects even after the official end of the internship.&lt;/p&gt;
&lt;h2&gt;In search of a problem&lt;/h2&gt;
&lt;p&gt;In order to get a deeper insight into the use of QF-Test at geo-related companies, I conducted several interviews with customers. On the one hand, the questions were aimed at general topics, such as whether there are special requirements for the geospatial industry in the area of quality assurance. On the other hand, I asked what daily work with QF-Test looks like and which processes are tested.&lt;/p&gt;
&lt;p&gt;Another question was about future use: Is there room for optimization? Which areas might perhaps not yet have a working solution? With these questions I also hoped to find a main project for my practical semester.&lt;/p&gt;
&lt;p&gt;The result of the interviews was that in most cases QF-Test already works great and is used in many ways. There were only a few difficulties concerning QF-Test, for example with the image comparison (as this can be unstable and time-consuming to maintain).&lt;/p&gt;
&lt;p&gt;The decisive idea for my project came from Uwe Päsler from the &amp;#8220;Staatsbetrieb Geobasisinformation und Vermessung Sachsen&amp;#8221; (&lt;a href="https://www.geosn.sachsen.de/"&gt;GeoSN&lt;/a&gt;). He suggested among other things to improve the procedure for the comparison of XML files which is part of QF-Test.&lt;/p&gt;
&lt;p&gt;Since the data exchange of the applications used by GeoSN mostly takes place via a NAS interface, corresponding tests have been carried out using XML files for some time. In addition to the informational and geometric data of the individual objects, these XML files also contain references of the objects to each other, which leads to very time-consuming tests with correspondingly large files (10 MB and up).&lt;/p&gt;
&lt;h2&gt;An improved procedure for comparing large XML files&lt;/h2&gt;
&lt;p&gt;For a long time it was possible to compare XML files via the procedure &lt;code&gt;qfs.utils.xml.compareXMLFiles&lt;/code&gt; of the standard library &lt;code&gt;qfs.qft&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The procedure provides a variety of ways to customize the comparison, such as ignoring nodes or attributes.&lt;/p&gt;
&lt;p&gt;The previous comparison worked via a Jython script that parses the files using the &lt;a href="https://docs.python.org/3/library/xml.dom.minidom.html"&gt;minidom parser&lt;/a&gt; and then compares them. The problem with minidom is that before the files can be compared, they are processed fully and represented in a DOM tree. This leads to a very high memory requirement for large files, which also explains the long runtime of it&amp;#8217;s previous comparison.&lt;/p&gt;
&lt;p&gt;With my new script, the speed problem is now solved. The comparison is no longer implemented using the minidom parser, but is based on the &lt;a href="https://docs.python.org/3/library/xml.sax.html"&gt;SAX parser&lt;/a&gt;. The abbreviation &amp;#8220;SAX&amp;#8221; stands for &amp;#8220;The Simple Application Programming Interface for XML&amp;#8221; and it&amp;#8217;s a Python library just like minidom. In contrast to the minidom parser, the SAX parser reads the file line by line and attribute by attribute. This and the structure of the script prevents the entire file from getting loaded into memory and thus speeds up the comparison enormously, so that it only takes about one minute for a 10 MB file!&lt;/p&gt;
&lt;p&gt;The comparison with minidom quickly reached a CPU utilization of over 90 percent for large files, or exhausted the RAM available to QF-Test. This could cause the program to crash, or to abort the comparison with an error message.&lt;/p&gt;
&lt;p&gt;In my new version, each node of a file is read by a thread and stored in a special queue. There is a separate thread for each of the two files, which takes care of the parsing. A third parallel thread compares the contents of the two queues simultaneously and clears them afterwards. This ensures that the amount of memory required is kept within reasonable limits.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/blog/resources/xml-sax-qfs-qft.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The new SAX-based XML comparison function as seen in the QF-Test standard library.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Efficient sorting&lt;/h2&gt;
&lt;p&gt;All functionalities offered by the previous procedure are also offered in the new procedure.&lt;/p&gt;
&lt;p&gt;Sorting before comparing is also possible. However, the content of the file is stored differently, namely in a custom class derived from &lt;code&gt;TreeMap&lt;/code&gt;. This &lt;code&gt;TreeMap&lt;/code&gt; consists again of at least two &lt;code&gt;TreeMap&lt;/code&gt;s. In these, the nodes of a given level are stored. As soon as the original node contains a child node, a new instance of this &lt;code&gt;TreeMap&lt;/code&gt; is created and stored in the &lt;code&gt;child node TreeMap&lt;/code&gt; of the original node. In the end you get a single &lt;code&gt;TreeMap&lt;/code&gt; which holds the entire file. Now you can iterate through it and compare the individual contents.&lt;/p&gt;
&lt;p&gt;The reason why a &lt;code&gt;TreeMap&lt;/code&gt; is used here is that the contents can already be stored sorted by a comparator at runtime. The &lt;code&gt;TreeMap&lt;/code&gt; also makes the process of input and output very fast.&lt;/p&gt;
&lt;p&gt;Since the entire file must still be kept in memory, the previous sorting increases the duration of the comparison, although it is of course still faster than the same action with the previous minidom process.&lt;/p&gt;
&lt;h2&gt;Conclusion of my practical semester&lt;/h2&gt;
&lt;p&gt;Besides this big project of my internship semester, I did many smaller tasks and activities. These made the period highly varied and interesting.&lt;/p&gt;
&lt;p&gt;During the entire internship semester, I also had support from my supervisor, who helped me a lot with the implementation of this project.&lt;/p&gt;
&lt;p&gt;Thanks to this internship, I was able to apply, deepen and expand the skills I had acquired during my previous studies. Especially the cooperation with QFS customers was exciting and I was able to gain many new impressions.&lt;/p&gt;
&lt;p&gt;PS from a colleague: Sarah&amp;#8217;s work is very valuable and useful for many who work with XML data. Therefore, her solution has been built into QF-Test and released as part of the standard library for all with &lt;a href="https://www.qftest.com/doc/manual/en/history.html"&gt;version 6.0.2.&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>QF-Test license information explained</title><link href="https://www.qftest.com/en/blog/article/license-info-explained.html" rel="alternate"/><published>2022-10-10T00:00:00+02:00</published><updated>2022-10-10T00:00:00+02:00</updated><author><name>Mike Schmidt</name></author><id>tag:www.qftest.com,2022-10-10:/en/blog/article/license-info-explained.html</id><summary type="html">&lt;p&gt;The version number of QF-Test and in the license file &amp;ndash; How are they related?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Let’s imagine that a new QF-Test version has just been released. And you were now wondering if your current license is valid for this version.&lt;/p&gt;
&lt;p&gt;To get the answer to this question, you would just have to open the &amp;#8220;QF-Test information&amp;#8221; dialog. You can do this via &amp;#8220;Help&amp;#8221; &amp;gt; &amp;#8220;Info…&amp;#8221; (in macOS &amp;#8220;QF-Test&amp;#8221; &amp;gt; &amp;#8220;About&amp;#8221;). There you will be presented with the contents of your current &lt;a href="/en/blog/article/license-licensenew-licensetxt-licensedat-licenseold-licenseserver-whos-gonn.html"&gt;license file&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="License Info" src="/blog/resources/license-info-en.png" /&gt;&lt;/p&gt;
&lt;h2&gt;That’s a whole lot of information! Who is supposed to understand this?&lt;/h2&gt;
&lt;p&gt;Don’t panic! Let’s go through the highlighted sections together and explain.&lt;/p&gt;
&lt;h3&gt;&amp;#8220;The license expires: Never&amp;#8221;&lt;/h3&gt;
&lt;p&gt;The most common case is where it says &amp;#8220;Never&amp;#8221;, meaning the license is valid for an unlimited period of time.&lt;/p&gt;
&lt;p&gt;However, there are also subscription licenses that are only valid until a certain date.&lt;/p&gt;
&lt;p&gt;We also ship licenses before payment is received, so that the customer can (continue to) work immediately. These interim licenses can also only be used for a limited period of time and will be replaced by the unlimited licenses after payment has been received.&lt;/p&gt;
&lt;h3&gt;&amp;#8220;It is valid for QF-Test versions –6.0&amp;#8221;&lt;/h3&gt;
&lt;p&gt;QF-Test licenses are generally valid for an unlimited period of time (except for subscription, interim and test licenses).&lt;/p&gt;
&lt;p&gt;The license in the example can therefore be used with QF-Test versions that are older than 6.0 or belong to the &amp;#8220;6.0 series&amp;#8221; (i.e. versions that were released or will be released before version 6.1.0).&lt;/p&gt;
&lt;p&gt;The version series specified here is always the most recent one at the time of ordering the license. You do not need a maintenance contract to use these versions.&lt;/p&gt;
&lt;h3&gt;&amp;#8220;… and all QF-Test versions, which are released up to 12/31/2023.&amp;#8221;&lt;/h3&gt;
&lt;p&gt;Now, there is also the maintenance contract, which not only includes the entitlement for timely and comprehensive support, but also the entitlement to additionally use all version series of QF-Test that are going to be released during the maintenance period without any further costs.&lt;/p&gt;
&lt;p&gt;The date specified here is the end of your current maintenance period. All QF-Test versions released within this period (or before) may be used with this license.&lt;/p&gt;
&lt;h3&gt;License file: …\licenses\license&lt;/h3&gt;
&lt;p&gt;Here you can find out where exactly your license file is stored, for example in case you need it again for a new computer or want to distribute the newly created file after a renewal of the maintenance contract.&lt;/p&gt;
&lt;p&gt;Note: In the screenshot you can see a different location than the default. So you will have a different path.&lt;/p&gt;
&lt;h2&gt;But what happens when my maintenance contract expires?&lt;/h2&gt;
&lt;p&gt;A maintenance contract is valid for a limited period of time (usually one year) and, if you agree, is renewed before it expires.&lt;/p&gt;
&lt;p&gt;Since the license file cannot &amp;#8220;know&amp;#8221; whether you have renewed your contract, we send appropriate &lt;a href="/en/blog/article/license-licensenew-licensetxt-licensedat-licenseold-licenseserver-whos-gonn.html"&gt;license update files (&lt;code&gt;license.new&lt;/code&gt;)&lt;/a&gt;automatically after the maintenance period has been extended.&lt;/p&gt;
&lt;p&gt;You should &lt;a href="https://www.youtube.com/watch?v=pm5lhuGD5UQ&amp;amp;t=16s"&gt;import&lt;/a&gt; this update file &amp;ndash; even if you are using an older QF-Test version. The new license file created in this way then only needs to be saved and distributed to all affected workstations (you now know how you would find the location of this license file).&lt;/p&gt;
&lt;h2&gt;All right, all right. But why is there a version number in the license at all?&lt;/h2&gt;
&lt;p&gt;The version information within the license file is still included for backwards compatibility with QF-Test versions prior to 4.1.&lt;/p&gt;
&lt;p&gt;Take a look at the contents of our example license file:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;product&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="nx"&gt;qftest&lt;/span&gt;
&lt;span class="nx"&gt;name&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="nx"&gt;company&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="nx"&gt;Quality&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Software&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GmbH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Geretsried&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Deutschland&lt;/span&gt;
&lt;span class="k"&gt;type&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="nx"&gt;Commercial&lt;/span&gt;
&lt;span class="nx"&gt;versions&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="o"&gt;-&lt;/span&gt;&lt;span class="m m-Double"&gt;6.0&lt;/span&gt;
&lt;span class="nx"&gt;expires&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="nx"&gt;concurrent&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="mi"&gt;5&lt;/span&gt;
&lt;span class="nx"&gt;awt&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="mi"&gt;5&lt;/span&gt;
&lt;span class="nx"&gt;win&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="mi"&gt;5&lt;/span&gt;
&lt;span class="nx"&gt;android&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="mi"&gt;5&lt;/span&gt;
&lt;span class="nx"&gt;runtime&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="mi"&gt;2&lt;/span&gt;
&lt;span class="nx"&gt;rtawt&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="mi"&gt;2&lt;/span&gt;
&lt;span class="nx"&gt;rtwin&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="mi"&gt;2&lt;/span&gt;
&lt;span class="nx"&gt;rtandroid&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="mi"&gt;2&lt;/span&gt;
&lt;span class="nx"&gt;ext9&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="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;maxUpdate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2023-12-31&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;replaces&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="nx"&gt;key&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="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;B9F68262556A671&lt;/span&gt;
&lt;span class="nx"&gt;signature&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="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Older QF-Test versions are not yet able to evaluate the line starting with &lt;code&gt;ext9 =&lt;/code&gt;, and thus neither the information about the maintenance contract contained therein. And indeed, some customers still use such old QF-Test versions for various reasons (for example, because they still have to use an outdated Java version).&lt;/p&gt;
&lt;p&gt;Therefore, we continue to include the current version of QF-Test at the time of license creation in &lt;code&gt;versions&lt;/code&gt;, so that even very old QF-Test versions can be operated with a current license.&lt;/p&gt;</content><category term="blog"/><category term="license"/></entry><entry><title>From mgm’s coffee kitchen to test automation with QF-Test</title><link href="https://www.qftest.com/en/blog/article/from-mgms-coffee-kitchen-to-test-automation-with-qf-test.html" rel="alternate"/><published>2022-09-22T00:00:00+02:00</published><updated>2022-09-22T00:00:00+02:00</updated><author><name>Thomas Max</name></author><id>tag:www.qftest.com,2022-09-22:/en/blog/article/from-mgms-coffee-kitchen-to-test-automation-with-qf-test.html</id><summary type="html">&lt;p&gt;Lilia Gargouri is head of the mgm Quality Team together with Martin Varendorff. In this interview, she describes for us how she found her way to QF-Test, what&amp;#8217;s important to her in test automation, and about her hopes for the closer cooperation between our companies in the future.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Lilia Gargouri is a project manager on the &lt;a href="https://insights.mgm-tp.com/en/2023/testing/deepdive-a12-end-to-end-test-automation/" title="mgm Quality Team"&gt;&lt;strong&gt;mgm Quality Team&lt;/strong&gt;&lt;/a&gt;. In this interview, she describes how she found her way to QF-Test, what’s important to her in test automation, and her hopes for the closer cooperation between our companies in the future.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;QFS: Lilia, we’ve been working together across companies for many years. How and when exactly did you find your way to QF-Test?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Over nine years ago now, during one of the testing phases of my project at the time, I overheard in the coffee kitchen that our QA department was being faulted for a single persistent yellow job in our automated testing pipeline. I was a senior software developer at the time and had little to do with quality assurance. So I spontaneously and voluntarily offered my help to QA. And so, on June 27, 2013, I received a link with installation instructions for QF-Test.&lt;/p&gt;
&lt;p&gt;That day was the beginning of a success story for me with QF-Test at mgm.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;QFS: What spontaneously comes to mind when you think of test automation with QF-Test?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ease of use. It’s very visual. Self-explanatory. Very pleasant. I quickly found my way around the tool. I didn’t need any training for beginners.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;QFS: What is your philosophy when it comes to test automation?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It was immediately clear to me that test automation is also software development. Test automation projects need to be set up in a structured way so that they remain readable, maintainable, and extensible over the years. If the package structure mirrors the UI structure, and the parameter names of procedures follow the order and names in the UI, then no one needs to be trained in the code. For this, names of procedures and test cases must be meaningful and instantly indicate their primary function.&lt;/p&gt;
&lt;p&gt;Each test case must be seen as a unit and must be executable at any time, independent of the test cases before and after it.&lt;/p&gt;
&lt;p&gt;Redundant code kills a test project faster than you think. Centralizing code is a very important key to success. It greatly reduces maintenance efforts.&lt;/p&gt;
&lt;p&gt;Here are a few key questions I ask myself during development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is the focus of this test case?&lt;/li&gt;
&lt;li&gt;What about implicit and explicit testing? Do I really need this step if an upcoming step covers the same implicitly anyway.&lt;/li&gt;
&lt;li&gt;How do I keep track of the quality of the whole project? Do I have good test coverage?&lt;/li&gt;
&lt;li&gt;How do I implement test cases that cover a lot, are easily maintainable and don’t take too long to execute?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Lilia from mgm and Thomas from QFS together in one of their many video call sessions" src="/blog/resources/Lilia_Thomas_CWR_edited.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Lilia from mgm and Thomas from QFS together in one of their many video call sessions&lt;/p&gt;
&lt;p&gt;&lt;em&gt;QFS: What features of QF-Test do you find most helpful, especially in terms of test development?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The introduction of &lt;strong&gt;Scopes&lt;/strong&gt; and &lt;strong&gt;SmartIDs&lt;/strong&gt; was a critical leap for QF-Test. It has opened the gates wide for generic solutions. Scope and SmartIDs are very easy to explain, understand and use. They are also super powerful, easy to maintain, parameterize, centralize, and bring a lot of accuracy and stability.&lt;/p&gt;
&lt;p&gt;I switched all the test automation projects I was managing at the time to SmartIDs three months after my introduction to Scopes and SmartIDs. I also implemented a model-based software solution &amp;#8220;&lt;a href="https://insights.mgm-tp.com/en/2023/testing/deepdive-a12-end-to-end-test-automation/" title="ATA (automated test automation)"&gt;automated test automation tool&lt;/a&gt;&amp;#8221; that generates test code for model-based applications and uses QF-Test to automatically populate and validate the UI. This significantly reduced the implementation and maintenance efforts in the projects. After this, QF-Test and me became obviously unstoppable at mgm.&lt;/p&gt;
&lt;p&gt;Analogous to the scopes and SmartIDs, I find the mappings in the &lt;strong&gt;CustomWebResolver&lt;/strong&gt; very powerful. The accuracy to recognize a project specific component on the UI and render it in a stable manner can be controlled there. I discovered &lt;code&gt;#Class\:MyOwnType:&lt;/code&gt; as well as &lt;code&gt;#MyOwnClass:&lt;/code&gt; there and can address many things more precisely without my extensions affecting other types of the same class. Utterly powerful and very exciting!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;QFS: How have you experienced the team at QFS behind QF-Test over the years?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Totally nice people. Very friendly, patient and knowledgeable. The exchange with the QFS team was always fruitful. I have learned a lot over the years. I made the solutions I developed with the team at QFS available internally to the mgm projects in a central test automation FAQ. QFS reminds me a lot of mgm when I joined 22 years ago. Maybe that’s why I felt very comfortable with the QFS team from the beginning.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;QFS: A few weeks ago we &lt;a href="/en/company/news/company-events.html"&gt;celebrated a &amp;#8220;wedding&amp;#8221;&lt;/a&gt; between mgm Technology Partners and Quality First Software, in the future we’ll work together even more closely. What do you think about this?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I knew about this years ago when it was just an idea. To me, it was overdue.&lt;/p&gt;
&lt;p&gt;Now that QFS is part of the mgm family, more new doors are opening for innovations, visions and mega-exciting features.&lt;/p&gt;
&lt;p&gt;I’m very much looking forward to a promising future together. Test automation, now more than ever!&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>Why Can’t Developers and Testers Just Get Along?</title><link href="https://www.qftest.com/en/blog/article/why-cant-developers-and-testers-just-get-along.html" rel="alternate"/><published>2022-05-31T00:00:00+02:00</published><updated>2022-05-31T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2022-05-31:/en/blog/article/why-cant-developers-and-testers-just-get-along.html</id><summary type="html">&lt;p&gt;The relationship between software testers and software developers can fraught with tension. But if everyone is free to bring their skills and their focus to the table, together, we can build great software.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The relationship between software testers and software developers is fraught with tension. More often than not, it&amp;#8217;s defined by condescension and grudging dependence on both sides.&lt;/p&gt;
&lt;p&gt;On one hand, testers sometimes suffer from the widely-held idea that testing software is generally a less difficult or demanding task than developing software. This is exacerbated by many companies making the &amp;#8220;rise&amp;#8221; from QA engineer to &amp;#8220;real&amp;#8221; software engineer a big step on their internal career ladder.&lt;/p&gt;
&lt;p&gt;Testers also frequently have to petition development for &amp;#8220;extra&amp;#8221; features such as backwards compatibility and accessibility descriptors, which are at times perceived by developers as superfluous and tedious to implement.&lt;/p&gt;
&lt;p&gt;Developers on the other hand often only hear from QA if there is a perceived problem with their work. Nobody likes to hear they made a mistake, and not everyone can always handle criticism gracefully.&lt;/p&gt;
&lt;p&gt;But developers actually depend on the diligence and knowledge of the QA department. I have personally witnessed how developers can build &amp;#8220;technically correct&amp;#8221; software without actually understanding the underlying use case. This results in idiosyncrasies in the software that are hard to spot without intimate knowledge of the business domain.&lt;/p&gt;
&lt;p&gt;The question is: What can software businesses do to ease this tension?&lt;/p&gt;
&lt;h2&gt;Can we make building software so easy everyone can do this?&lt;/h2&gt;
&lt;p&gt;One idea might be to try and make traditional &amp;#8220;software development&amp;#8221; skills unnecessary for building new applications.&lt;/p&gt;
&lt;p&gt;This was the aim of the &amp;#8220;Low Code&amp;#8221; movement. The idea, sometimes also called &amp;#8220;visual programming&amp;#8221; was to build &amp;#8220;meta programs&amp;#8221;, toolkits for building software for a specific domain out of visual blocks, templates, and maybe simple scripts.&lt;/p&gt;
&lt;p&gt;This was motivated by the ongoing shortage of software engineers. This shortage leads to companies being unable to progress the software they use or sell at a sufficient pace to keep up with demand and competition.&lt;/p&gt;
&lt;p&gt;Instead of building each piece of software in code from scratch, software testers (or even managers and end-users) would be able to use a &amp;#8220;no code&amp;#8221; or &amp;#8220;low code&amp;#8221; tool to piece together new software for their needs from existing building blocks, reducing the job of software engineers to the programming of the initial tool.&lt;/p&gt;
&lt;p&gt;This resulting software would still need to be thoroughly tested, of course, to make sure it meets the initial specifications. But the skill set of the software tester, with their intimate knowledge of what the software should &lt;em&gt;do&lt;/em&gt;, is suited well to both building and testing low-code software.&lt;/p&gt;
&lt;p&gt;But the dream of making building software as easy as editing a spreadsheet has largely failed.&lt;/p&gt;
&lt;p&gt;Most software requirements are, in the details, too complex and specific to fit into any modular system. In the best case, it&amp;#8217;s possible to build software that &lt;em&gt;kind of&lt;/em&gt; meets the original specs, but is filled with compromises, dumbed down and clunky to use. Software from a modular system will always require its users to adapt to it, instead of serving its users.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://unsplash.com/photos/assorted-color-wooden-blocks-RcxR1aLw8X0"&gt;&lt;img alt="Toy blocks" src="/blog/resources/tester-entwickler-michal-bozek-toy-blocks-unsplash.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Software built with low-code tools is cheap and simple, but usually not very powerful or flexible. Photo: Michał Bożek, https://unsplash.com/photos/RcxR1aLw8X0&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Some of these &amp;#8220;low code&amp;#8221; ideas have stuck around in modern website building services and some process automation tools, and found success in these limited scopes.&lt;/p&gt;
&lt;p&gt;But they are by far not the magic bullet to enable testers to get by without relying on developers.&lt;/p&gt;
&lt;h2&gt;Can we make testing software so convenient every developer can do this?&lt;/h2&gt;
&lt;p&gt;So, what about resolving our tester-developer-conundrum in the other direction? Can we make &lt;em&gt;testing&lt;/em&gt; software so convenient for developers that they can take care of it by themselves?&lt;/p&gt;
&lt;p&gt;Most software developers today know about the importance of testing. Many even use testing as a core part of their development workflow, as in test-driven development.&lt;/p&gt;
&lt;p&gt;But developers prefer the kinds of tests that they can write, run, and maintain with their accustomed tools. Developers write tests in code; unit tests that run quickly and headlessly.&lt;/p&gt;
&lt;p&gt;And developers usually only test the code they are working on and are responsible for. Rarely does it naturally fit into a developer&amp;#8217;s workflow to write integration tests between systems from different developers or teams, to say nothing of end-to-end tests.&lt;/p&gt;
&lt;p&gt;Test-driven development also usually does not care about behavior on other hardware or operating systems, or on legacy hardware or software. &amp;#8220;Works on my machine&amp;#8221; is the mantra for the types of tests developers write to aid in their development.&lt;/p&gt;
&lt;p&gt;If they are solely responsible for testing, developers will also tend to avoid writing code that is difficult to test with their preferred testing methods. Most importantly, this means they will write as little UI code as possible and avoid statefulness wherever they can.&lt;/p&gt;
&lt;p&gt;The end result is software like a command line interface: technically correct, but hard to use and understand.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Grep Man" src="/blog/resources/tester-entwickler-grep-man.png" /&gt;
 Command line tools are a typical developer-driven style of application.&lt;/p&gt;
&lt;h2&gt;Can we meet in the middle?&lt;/h2&gt;
&lt;p&gt;What we have just explored are not two solutions, but more like two software quality nightmare scenarios. What we actually should do is try to combine the best qualities of both approaches.&lt;/p&gt;
&lt;p&gt;We need two things for this: Communication and respect between testers and developers.&lt;/p&gt;
&lt;p&gt;The problem between testers and developers are not technical, they are organizational. And so they need an organizational solution.&lt;/p&gt;
&lt;p&gt;Companies should foster a positive relationship between testers and developers. QA and Dev teams should be encouraged to work together and go a mile in each other&amp;#8217;s shoes. Developers must be made aware of the tools and workflows QA uses for their work. Testers should gain at least a cursory understanding of the way the software is built. And companies should be structured in a way as to not encourage competition between teams, but communicate that everyone is working towards a common goal.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tester and developer: Friend or foe?" src="/blog/resources/tester-entwickler-devs-testers-friends.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Can&amp;#8217;t developers and testers just get along?&lt;/p&gt;
&lt;p&gt;I know this is much easier said than done. But the divide between testers and developers is not as big as it may seem at times. If each is free to bring their skills and their focus to the table, together, we can build great software.&lt;/p&gt;
&lt;p&gt;&lt;a href="/en/company/references/testimonials/benefits-for-developers.html"&gt;Advantages of QF-Test for developers&lt;/a&gt; and &lt;a href="/en/company/references/testimonials/benefits-for-testers.html"&gt;advantages for testers&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>Our kicker – once again, one of the most important “employees”</title><link href="https://www.qftest.com/en/blog/article/our-kicker-once-again-one-of-the-most-important-employees.html" rel="alternate"/><published>2022-05-05T00:00:00+02:00</published><updated>2022-05-05T00:00:00+02:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2022-05-05:/en/blog/article/our-kicker-once-again-one-of-the-most-important-employees.html</id><summary type="html">&lt;p&gt;On Wednesday the 27th of April 2022 at the career fair in Bad Tölz QFS was represented as an exhibitor with Yann Spöri, Martina Schmid and the aforementioned company kicker.&lt;/p&gt;</summary><content type="html">&lt;p&gt;On Wednesday the 27th of April 2022 at the career fair in Bad Tölz. QFS was represented as an exhibitor with Yann Spöri, Martina Schmid and the  aforementioned company kicker. Young people from different schools visited us &amp;ndash; admittedly mostly attracted by the table soccer. But also serious conversations. Interest in computer science is high, but QFS does not offer However, QFS does not offer the often sought-after IT specialist training. &lt;br /&gt;
 Our offers are aimed at students of MINT/Informatics courses of study - The first application in this direction from a visitor reached us just one day later. &lt;br /&gt;
 reached us just one day later. It may also be due to the young age and that hardly anyone with programming knowledge found their way to us. The chosen day of the fair was somewhat unfortunate for both QFS and a handful of a handful of exhibiting colleges: High school graduates did not come, the school-leaving exams in Bavaria started on that day.&lt;/p&gt;
&lt;p&gt;In addition to the young people, however, the kicker was also for ourselves and some other exhibitors a welcome occasion for a sports session. The temperatures at the trade fair venue, the freshly thawed ice rink, were somewhat low. Heated battles kept spirits and body temperatures high and the QFS team also put up a good fight in a narrow 9:10 loss against the neighboring team from the customs, which competed with a former student world champion.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Career fair Bad Tölz &amp;ndash; Yann" src="/blog/resources/2022-berufsmesse-bad-toelz-yann.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Career fair Bad Tölz &amp;ndash; our Kicker" src="/blog/resources/csm_2022-berufsmesse-bad-toelz-kicker_99c49c6be9.jpg" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Testing Content Security Policy (CSP) protected web pages with Firefox</title><link href="https://www.qftest.com/en/blog/article/testing-content-security-policy-csp-protected-pages-with-firefox.html" rel="alternate"/><published>2022-05-03T00:00:00+02:00</published><updated>2022-05-03T00:00:00+02:00</updated><author><name>Pascal Bihler</name></author><id>tag:www.qftest.com,2022-05-03:/en/blog/article/testing-content-security-policy-csp-protected-pages-with-firefox.html</id><summary type="html">&lt;p&gt;Strong CSP rules make it more difficult to test them with Firefox from version 99 on.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://content-security-policy.com/"&gt;Content Security Policies&lt;/a&gt; are a powerful tool to make cross site scripting attacks on web sites and web applications difficult. The web server sends response headers with one or more &lt;code&gt;Content-Security-Policy&lt;/code&gt; header rows to the browser, defining explicitly for example which Javascript code may be executed in the context of the web page.&lt;/p&gt;
&lt;p&gt;When you test a web application with QF-Test, it has to interact closely with the application in order to recognize the components or to execute checks or to be able to record a test. QF-Test executes for example Javascript code in the context of the web page via the &lt;a href="https://www.w3.org/TR/webdriver1/#executing-script"&gt;WebDriver interface&lt;/a&gt; of QF-Test &amp;ndash; and here a strict CSP rule may interfere. Therefore, QF-Test starts the browser in a mode which ignores the content security policy header rows.&lt;/p&gt;
&lt;p&gt;Unfortunately, this is no longer possible as easily with Firefox &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1754301"&gt;since version 99&lt;/a&gt; &amp;ndash; with the result that QF-Test will come up with a &lt;code&gt;DocumentNotLoaded&lt;/code&gt; exception for applications thus protected.&lt;/p&gt;
&lt;p&gt;In order to avoid the problem, basically, you have three options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Execute the web application in a &amp;#8220;test mode&amp;#8221; without CSP headers.&lt;/li&gt;
&lt;li&gt;Remove the CSP headers via a proxy.&lt;/li&gt;
&lt;li&gt;Remove the CSP headers via a browser add-on.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Execute the application in &amp;#8220;test mode&amp;#8221;&lt;/h2&gt;
&lt;p&gt;The &amp;#8220;test mode&amp;#8221; is the best solution when you have influence on the web application &amp;ndash; starting the application on a test server in a special, &amp;#8220;CSP free&amp;#8221; mode. Then, the problems will not occur anymore when testing with QF-Test.&lt;/p&gt;
&lt;h2&gt;Remove the CSP headers via proxy&lt;/h2&gt;
&lt;p&gt;At the start of a web test you can specify a proxy via which to load the web pages in the parameters &lt;code&gt;proxyAddress&lt;/code&gt; and &lt;code&gt;proxyPort&lt;/code&gt; of the procedure call to &lt;a href="/include/qfs_pkgdoc/qfs_pkgdoc.html#qfs.web.browser.settings.doStartupSettings"&gt;&lt;code&gt;qfs.web.browser.settings.doStartupSettings&lt;/code&gt;&lt;/a&gt;. In the proxy you can then remove the impeding response headers, for example with &lt;a href="https://serverfault.com/questions/928912/how-do-i-remove-a-server-added-header-from-proxied-location"&gt;nginx&lt;/a&gt;, &lt;a href="https://www.metahackers.pro/spoof-http-header-using-squid-proxy/"&gt;squid&lt;/a&gt;, &lt;a href="https://docs.mitmproxy.org/stable/overview-features/#modify-headers"&gt;mitmproxy&lt;/a&gt; or with the Java proxy &lt;a href="https://github.com/lightbody/browsermob-proxy/issues/213#issuecomment-98567883"&gt;browsermob-proxy&lt;/a&gt; which can even be configured via SUT scripts.&lt;/p&gt;
&lt;h2&gt;Remove the CSP headers via browser add-on&lt;/h2&gt;
&lt;p&gt;The third option is to remove the header in the browser via an add-on, for example with &lt;a href="https://modheader.com/"&gt;ModHeader&lt;/a&gt; or with &lt;a href="https://addons.mozilla.org/de/firefox/addon/simple-modify-header/"&gt;simple-modify-headers&lt;/a&gt;. This is the approach recommended &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1754301#c10"&gt;by Mozilla developers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Installation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new setup sequence with the quickstart assistant of QF-Test opening the test page &lt;a href="https://content-security-policy.com/browser-test/"&gt;https://content-security-policy.com/browser-test/&lt;/a&gt;. When all the boxes on the page are shown in red everything is all right (for the test), if they are green CSP is not being ignored.&lt;/li&gt;
&lt;li&gt;Navigate &lt;em&gt;in that browser&lt;/em&gt; to the installation site of the add-on, for example &lt;a href="https://addons.mozilla.org/de/firefox/addon/simple-modify-header/"&gt;https://addons.mozilla.org/de/firefox/addon/simple-modify-header/&lt;/a&gt;. Since QF-Test uses a dedicated profile for the tests it is important the installation will be done with QF-Test profile.&lt;/li&gt;
&lt;li&gt;Install the add-on via the button &amp;#8220;Add to Firefox&amp;#8221;.&lt;/li&gt;
&lt;li&gt;Now, an icon for configuration will appear next to the URL on the right. Click it to open the configuration page (if the browser starts to flicker you can close the first tab).&lt;/li&gt;
&lt;li&gt;Define a rule valid &lt;em&gt;for all URLs&lt;/em&gt; (&amp;#8220;Url Patterns*: *&amp;#8221;), with the action &amp;#8220;Delete&amp;#8221;, the Header Field Name &lt;code&gt;Content-Security-Policy&lt;/code&gt; and an empty Header Field Value being applied on server responses (&amp;#8220;Apply on Response&amp;#8221;). Click on &amp;#8220;Save&amp;#8221;.&lt;/li&gt;
&lt;li&gt;With &amp;#8220;simple modify headers&amp;#8221; you then have to activate the add-on via the &amp;#8220;START&amp;#8221; icon.&lt;/li&gt;
&lt;li&gt;Now, when you close the browser and run the setup sequence again, the boxes on the test page should all be red.&lt;/li&gt;
&lt;li&gt;And now, even with CSP protected applications you should not encounter any CSP problems anymore with testing.
&lt;img alt="Simple Modify headers rule" src="/blog/resources/csp-simple-modify-headers-rule.png" /&gt;
 Screenshot for Step 5.&lt;/li&gt;
&lt;/ol&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>The Blockchain Icon Mode – QF-Test out of blocks</title><link href="https://www.qftest.com/en/blog/article/the-blockchain-icon-mode-qf-test-out-of-blocks.html" rel="alternate"/><published>2022-04-01T00:00:00+02:00</published><updated>2022-04-01T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2022-04-01:/en/blog/article/the-blockchain-icon-mode-qf-test-out-of-blocks.html</id><summary type="html">&lt;p&gt;Today on first april we finally succeeded in a promising experiment for the automatic redesign of QF-Test &amp;ndash; how useful the blockchain technology can be.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Because some results of theoretical informatics (e.g. the halting problem) indicate that certain software properties are not calculatable &amp;ndash; and because AI-approaches like Lisp-machines, perceptrons etc. do have their limits (Vapnik–Chervonenkis Dimension) &amp;ndash; we managed to achieve a breakthrough in the redesign of our icons. A bunch of monkeys led by the monkey Mally have designed new icons for today&amp;#8217;s first of April, whereby the majority finding was guaranteed by a blockchain algorithm:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Blockchain mode" src="/blog/resources/blockchain-block-mode-en.png" /&gt;In case you prefer the traditional view, you can disable the icon block mode in the view menu:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Disable QF-Test Blockchain mode" src="/blog/resources/blockchain-disable-block-mode-en.png" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Welcome to JavaLand 2022</title><link href="https://www.qftest.com/en/blog/article/javaland-2022.html" rel="alternate"/><published>2022-03-29T00:00:00+02:00</published><updated>2022-03-29T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2022-03-29:/en/blog/article/javaland-2022.html</id><summary type="html">&lt;p&gt;In March of 2022, part of our development team visited the JavaLand conference. Here are some impressions from the event.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Lilienthal Keynote" src="/blog/resources/2022-javaland-lilienthal-keynote.jpg" /&gt;
 The conference keynote &amp;#8220;Common Misunderstandings in Software Architecture&amp;#8221; was held by by Dr. Carola Lilienthal
 Here I am, sitting in a cinema-style seat inside a huge auditorium, in front of a stage styled like an ancient library. Around me are almost a thousand enthusiastic people from all over the world. I&amp;#8217;m nervous, but excited: I have not been around this many people in a long, long time. But it feels good. The lights dim and the conference begins.&lt;/p&gt;
&lt;h2&gt;The Conference&lt;/h2&gt;
&lt;p&gt;The JavaLand conference is one of the largest Java conferences in Europe. Here, over a thousand professionals from all over the world come together to talk about all kinds of Java topcis.&lt;/p&gt;
&lt;p&gt;We at QFS were very excited to attend a JavaLand conference in person again this March, since that had not been possible since 2019 because of – well, you know why. Up until the last minute it was unclear if the conference would be allowed to happen as an in-person event, but in the end, a couple of developer colleagues and me took the wonky German train system across the country all the way to Cologne to learn about the latest trends in Java development and spend some quality time together and with other like-minded people. Also, there was the promise of riding roller-coasters… But more on that later.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QFS Team" src="/blog/resources/2022-javaland-team.jpg" /&gt;
 For some on the team, this conference was the first time we ever met in person
 For us, the biggest win of the conference was the social aspect: Colleagues from &lt;a href="/en/company/about-us.html"&gt;Geretsried and Leipzig&lt;/a&gt; finally got to meet live and in color, some even for the first time. Together, we had a ton of fun, but also learned a thing or the other about topics relevant to our daily work.&lt;/p&gt;
&lt;h2&gt;The Talks&lt;/h2&gt;
&lt;p&gt;&lt;img alt="JavaFland gRPC" src="/blog/resources/2022-javaland-reimer-grpc.jpg" /&gt;
 In &amp;#8220;REST in Peace. Long live gRPC&amp;#8221;, Mario-Leander Reimer explained why REST is great, but not as great as gRPC when it comes to communication between micro services
 In terms of &lt;a href="https://meine.doag.org/events/javaland/2022/agenda/"&gt;speaker sessions&lt;/a&gt;, there was a lot of talk about Microservice architectures and the no-longer-quite-new Java 17, also some talks about applications for the buzzword technologies AI and deep learning.&lt;/p&gt;
&lt;p&gt;&lt;img alt="JDK 17" src="/blog/resources/2022-javaland-ebbers-java17.jpg" /&gt;
 In &amp;#8220;JDK 17 – das neue LTS-Release ist da!&amp;#8221;, the history of Java was recapitulated by Wolfgang Weigend and Hendrik Ebbers
 Most interesting for our daily work at QFS were talks about Green Coding (assessing the &lt;em&gt;environmental impact&lt;/em&gt; of our software) and a talk about the &amp;#8220;State of the Java Metrics Libraries&amp;#8221; (because we&amp;#8217;re all about making sure code works well).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Balancing Cognitive Load in Teams" src="/blog/resources/2022-javaland-wojcieszak-cognitive-load.jpg" /&gt;
 &amp;#8220;Balancing Cognitive Load in Teams&amp;#8221; with Sabine Wojcieszak
 However, there were also some interesting talks about non-technical topics, such as a primer on the history of feminism, about balancing mental load in teams, and about &lt;a href="https://x.com/larsr_h/status/1504006681444896772"&gt;the things we don&amp;#8217;t talk about at work but maybe should&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The Park&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Main Street" src="/blog/resources/2022-javaland-avenue.jpg" /&gt;
 Looking down the main street of the theme park hosting JavaLand
 In case of JavaLand, having the conference in person is especially important because (besides the fact that it was nice to be able to interact with speakers directly) the whole conference takes place in a huge theme park. So when strolling from one session to the next, you walk through a lovely recreation of &lt;em&gt;Belle Époque&lt;/em&gt; France, or a Mexican canyon, or you are surrounded by a huge western-themed rollercoaster. There is also a Chinese garden. And a huge medieval castle tower permantently looms in the distance.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Javaland Mexico" src="/blog/resources/2022-javaland-mexico.jpg" /&gt;
 The entire theme park is filled with beautifully detailed worlds to explore
 This makes just moving from talk to talk very entertaining.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Javaland Dinner" src="/blog/resources/2022-javaland-fine-dining.jpg" /&gt;
 Fine dining at JavaLand 2022
 And almost all the restaurants and cafés were filled with free snacks, drinks, and food (which was pretty great by conference food standards).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Black Mamba" src="/blog/resources/2022-javaland-black-mamba.jpg" /&gt;
 The famous Black Mamba rollercoaster (sadly not open for business this time)
 And of course, the cherry on top of the conference was the evening when many of the rides all around the park opened their gates. All attendees were free to ride rollercoasters, explore fun houses, go on the merry-go-round, or have a beer at the open bar. The technically impressive &lt;a href="https://www.phantasialand.de/de/themenpark/einzigartige-attraktionen/crazy-bats/"&gt;&amp;#8220;Crazy Bats&amp;#8221; VR rollercoaster&lt;/a&gt; and the dizzyingly fast spinning coaster &lt;a href="https://www.phantasialand.de/de/themenpark/einzigartige-attraktionen/winjas-fear-force/"&gt;&amp;#8220;Winja&amp;#8217;s Fear&amp;#8221;&lt;/a&gt; were some favorites among colleagues.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Roller coasters" src="/blog/resources/2022-javaland-rollercoasters.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Many rides and venues were open free of charge for attendees on Tuesday evening&lt;/p&gt;
&lt;h2&gt;The Takeaways&lt;/h2&gt;
&lt;p&gt;&lt;img alt="QFS team" src="/blog/resources/2022-javaland-team-onthego.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The QFS team is on the go&lt;/p&gt;
&lt;p&gt;Personally, this was my first time attending a Java conference. I was impressed by the generally high quality of the talks and the absolutely seamless, professional organization of the event. Also, despite COVID-19 being on everyone&amp;#8217;s mind, the conference felt pretty safe, with lots of opportunities for fresh air and a laudable mask discipline among the attendees.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lindbergh Hotel" src="/blog/resources/2022-javaland-lindbergh-hotel.jpg" /&gt;
 JavaLand was hosted here this year.
 It really was a great mix of social get-together, learning new things, and getting a feeling for what&amp;#8217;s &amp;#8220;hot&amp;#8221; and &amp;#8220;cool&amp;#8221; right now in the Java community. I think I speak for all of us when I say that I&amp;#8217;m looking forward to being back at JavaLand next year, when it&amp;#8217;s time for another round of &amp;#8220;JATUMBA&amp;#8221;!&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>4 Quick Tips from the QF-Test Support Team</title><link href="https://www.qftest.com/en/blog/article/4-quick-tips-from-the-qf-test-support-team.html" rel="alternate"/><published>2022-02-28T00:00:00+01:00</published><updated>2022-02-28T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2022-02-28:/en/blog/article/4-quick-tips-from-the-qf-test-support-team.html</id><summary type="html">&lt;p&gt;Day in, day out, our diligent support team answers our customers&amp;#8217; questions. Here are some of their answers about data drivers, error message filtering, and variable defaults.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Day in, day out, our diligent support team reads and answers the questions of our customers coming in by mail. To pass on some of this collected support wisdom, here are some of the questions we were asked in the last weeks, as well as our answers.&lt;/p&gt;
&lt;h2&gt;How to &amp;#8220;Check text&amp;#8221; of a data driver item instead of a component&lt;/h2&gt;
&lt;p&gt;One of our customers had a data driver set up to query their database, and they wanted to use a &amp;#8220;Check text&amp;#8221; node to check the queried data instead of checking a component.&lt;/p&gt;
&lt;p&gt;Unfortunately, the &amp;#8220;Check text&amp;#8221; step always relates to a UI component as source. But you can easily achieve the same thing with a short &lt;a href="https://www.qftest.com/doc/manual/en/control.html#step_ClientScriptStep"&gt;SUT script&lt;/a&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;variableWithDataFromDataDriver&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;variableWithExpectedValue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Check if data driver returns expected value&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;How to compare two data sources to each other&lt;/h2&gt;
&lt;p&gt;This one also has to do with data drivers.&lt;/p&gt;
&lt;p&gt;A customer wanted to check database values against another dataset (in a data table). They noticed that nesting data drivers or placing two sources in one data driver did not have the desired effect. (Placing two sources in a data driver is defined as iterating over the cross product of both data sources, which is cool but not what we&amp;#8217;re looking for here).&lt;/p&gt;
&lt;p&gt;So, is there a way to iterate through the rows of a database using the iterator from the data table? Yes! To compare the elements in a data driver against elements from a database query, you can proceed like this:&lt;/p&gt;
&lt;p&gt;Before iterating over the actual data, use the procedure &lt;a href="/include/qfs_pkgdoc/qfs_pkgdoc.html#qfs.database.executeSelectStatement"&gt;&lt;code&gt;qfs.database.executeSelectStatement&lt;/code&gt;&lt;/a&gt; from the standard library to load the reference data into a property group &amp;#8220;expectedValues&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Then, inside the data driver loop you can perform the checks like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;variableWithDataFromDataDriver&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;expectedValues&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dataColumn:&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;counter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Check if data driver returns expected value&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;How to filter error messages from the QF-Test terminal&lt;/h2&gt;
&lt;p&gt;Another customer had a question about an error message (caused by their SUT) filling up the QF-Test terminal and making them miss other important log information.&lt;/p&gt;
&lt;p&gt;Even though QF-Test can not stop errors in an SUT it &lt;em&gt;is&lt;/em&gt; possible to instruct QF-Test to filter certain messages from the terminal output via an output filter.&lt;/p&gt;
&lt;p&gt;This can be done by adding the following &lt;a href="https://www.qftest.com/doc/manual/en/tech_doctags.html"&gt;doctag&lt;/a&gt; to the comment field of your &amp;#8220;Start Web Engine&amp;#8221; node:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;@outputFilter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;drop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;regular expression matching part of the message to filter&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;How to check if a variable is set at runtime&lt;/h2&gt;
&lt;p&gt;The last customer question for today is about variables. A customer wondered how to check if a &lt;a href="https://www.qftest.com/doc/manual/en/user_variables.html"&gt;QF-Test variable&lt;/a&gt; has previously been assigned a value during a test run.&lt;/p&gt;
&lt;p&gt;In general, accessing such an &amp;#8220;unbound&amp;#8221; variable will trigger an exception of type &amp;#8220;UnboundVariableException&amp;#8221;. You could always use a &amp;#8220;Try&amp;#8221; and &amp;#8220;Catch&amp;#8221; node to handle this exception. But there is a more elegant way for most use cases.&lt;/p&gt;
&lt;p&gt;Instead of accessing the variable with &lt;code&gt;$(myvariable)&lt;/code&gt; you can use the special &lt;a href="https://www.qftest.com/doc/manual/de/user_variables.html#usec_externaldata"&gt;&lt;code&gt;default&lt;/code&gt; group&lt;/a&gt; to define a fallback value in case the variable is not set: &lt;code&gt;${default:myvariable:MyFallbackValue}&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That&amp;#8217;s it for today! If you have your own question about using QF-Test you need answered, feel free to &lt;a href="/en/support/product-support.html"&gt;drop a line to our support team&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Running Website Tests in Parallel with QF-Test</title><link href="https://www.qftest.com/en/blog/article/running-website-tests-in-parallel-with-qf-test.html" rel="alternate"/><published>2022-02-15T00:00:00+01:00</published><updated>2022-02-15T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2022-02-15:/en/blog/article/running-website-tests-in-parallel-with-qf-test.html</id><summary type="html">&lt;p&gt;Running multiple tests at once could improve test execution time a lot (if raw system performance is not a bottleneck) while saving lots of infrastructure overhead because everything can run on one machine.&lt;/p&gt;</summary><content type="html">&lt;p&gt;From time to time, customers approach us asking if QF-Test can be used to run tests in parallel. The advantages of this are obvious: Running multiple tests at once could improve test execution time a lot (if raw system performance is not a bottleneck) while saving lots of infrastructure overhead because everything can run on one machine.&lt;/p&gt;
&lt;p&gt;Our answer for years has been that this &amp;#8220;is possible to a limited degree&amp;#8221;. Which is still true. But in this blog post, I want to dive a little deeper and see how and to &lt;em&gt;which degree exactly&lt;/em&gt; this can be done.&lt;/p&gt;
&lt;p&gt;The hardest problem of running &lt;em&gt;anything&lt;/em&gt; in parallel on one machine is the I/O interfaces. Your computer usually only has one mouse and keyboard attached for inputting things, and also only one desktop running at a time to display stuff.&lt;/p&gt;
&lt;p&gt;This means your test suites must stay within the following boundaries to allow them to run in parallel:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tests should not rely on hard system events for simulating keyboard or mouse input.&lt;/li&gt;
&lt;li&gt;Tests must not rely on an exclusive desktop or user session.&lt;/li&gt;
&lt;li&gt;Tests must be able to run in arbitrary order and must not depend on another.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Running a Test Suite in Multiple Threads&lt;/h2&gt;
&lt;p&gt;Launching a QF-Test test suite multiple times in parallel is the easy part: You can use the &lt;a href="https://www.qftest.com/doc/manual/en/user_execbatch.html"&gt;batch mode on the command line&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qftest -batch -threads 3 -run /Path/to/test/suite.qft&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will &lt;em&gt;kind of&lt;/em&gt; work, but the results will most likely be a jumbled mess, since each thread will try to launch and control the same client, which will eventually fail in some way. We still have more work to do.&lt;/p&gt;
&lt;h2&gt;Starting Separate Browser Clients per Thread&lt;/h2&gt;
&lt;p&gt;First we need to be able to launch separate browser clients for each thread. QF-Test separates clients by their name, so you can use the built-in &lt;code&gt;${qftest:thread}&lt;/code&gt; variable when naming your client: In the &amp;#8220;Variable Definitions&amp;#8221; section of your test suite root step, set &amp;#8220;client&amp;#8221; to something like &lt;code&gt;web${qftest:thread}&lt;/code&gt;. (Storing the client name in a variable called &lt;code&gt;$(client)&lt;/code&gt; is only a convention, so you have to make sure your test suite actually follows it.)&lt;/p&gt;
&lt;p&gt;Then, you should use something like the following Jython server script to configure every client browser with it&amp;#8217;s own separate profile for settings, cookies, and caches:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;client&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;profilepath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;-profile&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gettempdir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLocal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;profilepath&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;profilepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Benutze Profilpfad &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; für Client &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profilepath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dont forget to use the new &lt;code&gt;$(profilepath)&lt;/code&gt; variable in the &amp;#8220;Start Web Engine&amp;#8221; node to have each browser use a different temporary directory for profile data.&lt;/p&gt;
&lt;h2&gt;Browsers without User Session in Headless Mode&lt;/h2&gt;
&lt;p&gt;If you now launch your test suite in batch mode, you will see multiple browser windows showing up and all running through your tests. This is nice, but you can still see a big problem. The browser windows all &amp;#8220;steal&amp;#8221; the window focus from each other on every interaction, which will very likely screw with test stability. To work around this, you&amp;#8217;ll need to use a &lt;em&gt;headless browser&lt;/em&gt;, a browser which runs invisibly in the background, without requiring an actual window or desktop.&lt;/p&gt;
&lt;p&gt;To use a headless browser in your tests, set the browser type to &amp;#8220;headless&amp;#8221; (or &amp;#8220;headless-firefox&amp;#8221;, &amp;#8220;headless-chrome&amp;#8221;, or &amp;#8220;headless-edge), in the &amp;#8220;Browser type&amp;#8221; option of your &amp;#8220;Start Web Engine&amp;#8221; step. Be mindful that &lt;a href="https://www.qftest.com/doc/manual/en/user_loadtest.html#usec_loadtestingwebheadless"&gt;headless browsers don&amp;#8217;t support all features&lt;/a&gt; that normal browsers do.&lt;/p&gt;
&lt;p&gt;Alternatively, you can experiment with setting &lt;code&gt;rc.setOption(Options.OPT_WEB_ASSUME_HEADLESS, true)&lt;/code&gt; in a SUT script between &amp;#8220;Start Web Engine&amp;#8221; and &amp;#8220;Open Browser Window&amp;#8221; to tell QF-Test to treat a normal browser window as headless.&lt;/p&gt;
&lt;p&gt;If you run your test suite again, you will see &amp;#8230; nothing (which is a good thing in this case). You should probably add some debug logging to your suite in strategic places to make sure everything runs as expected. You can write to &lt;code&gt;stdout&lt;/code&gt; at any time via good old &lt;code&gt;println&lt;/code&gt; (or &lt;code&gt;print()&lt;/code&gt; in Jython).&lt;/p&gt;
&lt;h2&gt;Dividing Your Tests Between Threads&lt;/h2&gt;
&lt;p&gt;Until now, we have only been running the same tests in multiple clients. This is not the kind of parallel testing we&amp;#8217;re after here. We&amp;#8217;d like each of our threads to handle a subset of tests, not have all tests run on each thread.&lt;/p&gt;
&lt;p&gt;For this you will need to adjust your test structure itself. It is completely up to you how you want or can split up your tests. One very simple way is to use the modulo operator &lt;code&gt;%&lt;/code&gt; to split your test cases evenly between your number of threads. You can use the special variables &lt;code&gt;${qftest:thread}&lt;/code&gt; and &lt;code&gt;${qftest:threads}&lt;/code&gt; to figure out in which thread you are and how many threads there are in total and enclose each test case with an &amp;#8220;if&amp;#8221; conditional with a statement like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$(test_case_index) % ${qftest:threads} == ${qftest:thread}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Or, more elegantly, you could implement a Dispatcher via a &lt;a href="https://www.qftest.com/doc/manual/en/tech_testrunlisteners.html"&gt;TestRunListener&lt;/a&gt; at the beginning of your test suite with the following Groovy server script:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;synchronized(binding)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;    &lt;/span&gt;if&lt;span class="w"&gt; &lt;/span&gt;(!&lt;span class="w"&gt; &lt;/span&gt;binding.hasVariable(&amp;quot;handledSteps&amp;quot;))&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;        &lt;/span&gt;binding.setVariable(&amp;quot;handledSteps&amp;quot;,new&lt;span class="w"&gt; &lt;/span&gt;java.util.concurrent.ConcurrentHashMap())
&lt;span class="w"&gt;        &lt;/span&gt;binding.setVariable(&amp;quot;localStepIndices&amp;quot;,&lt;span class="w"&gt; &lt;/span&gt;ThreadLocal.withInitial({[:]}))
&lt;span class="w"&gt;        &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Registriere&lt;span class="w"&gt; &lt;/span&gt;TestRunListener
&lt;span class="w"&gt;        &lt;/span&gt;notifications.addObserver(notifications.NODE_ENTERED,&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;args&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt;
&lt;span class="w"&gt;            &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;step&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;args.event.getNode()
&lt;span class="w"&gt;            &lt;/span&gt;if&lt;span class="w"&gt; &lt;/span&gt;(step.getType()&lt;span class="w"&gt; &lt;/span&gt;==&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;TestCase&amp;quot;)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;thread&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;Thread.currentThread()
&lt;span class="w"&gt;                &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;handled&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;binding.getVariable(&amp;quot;handledSteps&amp;quot;)
&lt;span class="w"&gt;                &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;step.getUid()
&lt;span class="w"&gt;                &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;localStepIndices&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;binding.getVariable(&amp;quot;localStepIndices&amp;quot;)
&lt;span class="w"&gt;                &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;localStepIndex&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;localStepIndices.get().get(id)
&lt;span class="w"&gt;                &lt;/span&gt;localStepIndex&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;localStepIndex&lt;span class="w"&gt; &lt;/span&gt;==&lt;span class="w"&gt; &lt;/span&gt;null&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt; &lt;/span&gt;0&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;localStepIndex+1
&lt;span class="w"&gt;                &lt;/span&gt;localStepIndices.get().put(id,localStepIndex)
&lt;span class="w"&gt;                &lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;#&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;localStepIndex&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;

&lt;span class="w"&gt;                &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;existing&lt;span class="w"&gt; &lt;/span&gt;=&lt;span class="w"&gt; &lt;/span&gt;handled.putIfAbsent(id,thread)
&lt;span class="w"&gt;                &lt;/span&gt;if&lt;span class="w"&gt; &lt;/span&gt;(existing&lt;span class="w"&gt; &lt;/span&gt;!==&lt;span class="w"&gt; &lt;/span&gt;null)&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                    &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Überspringe&lt;span class="w"&gt; &lt;/span&gt;Testschritt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;wird&lt;span class="w"&gt; &lt;/span&gt;bereits&lt;span class="w"&gt; &lt;/span&gt;durch&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ausgeführt
&lt;span class="w"&gt;                    &lt;/span&gt;rc.skipTestCase()
&lt;span class="w"&gt;                &lt;/span&gt;}&lt;span class="w"&gt; &lt;/span&gt;else&lt;span class="w"&gt; &lt;/span&gt;{
&lt;span class="w"&gt;                    &lt;/span&gt;//&lt;span class="w"&gt; &lt;/span&gt;Führe&lt;span class="w"&gt; &lt;/span&gt;Testschritt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;aus
&lt;span class="w"&gt;                &lt;/span&gt;}
&lt;span class="w"&gt;            &lt;/span&gt;}
&lt;span class="w"&gt;        &lt;/span&gt;})
&lt;span class="w"&gt;    &lt;/span&gt;}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;We Did It! Or Did We?&lt;/h2&gt;
&lt;p&gt;If you added some &lt;code&gt;print&lt;/code&gt; statements to your test suite, you should now see that QF-Test will run your test batches out of order. Congratulations, you did it!&lt;/p&gt;
&lt;p&gt;Still, there remain some caveats.&lt;/p&gt;
&lt;p&gt;Firstly, when editing or adding tests in your suite, you will have to be very careful to not break any of the boundaries established at the start of this post.&lt;/p&gt;
&lt;p&gt;Also, the algorithm for splitting your tests between threads is far from optimal from a performance perspective. Different test batches could take very different amounts of time, resulting in idle threads. A more complete solution would include some dispatch scheduler to assign test batches to free threads on-demand.&lt;/p&gt;
&lt;p&gt;Working around all of these caveats can easily get very complicated. So complicated that it may in fact be faster, cheaper, and more stable to run multiple instances of QF-Test on separate machines or VMs instead, and use some existing dispatch infrastructure.&lt;/p&gt;
&lt;p&gt;So in the end, we come back to were we started. Parallel test execution inside of QF-Test really &lt;em&gt;is&lt;/em&gt; possible to a limited degree. But you should investigate if it&amp;#8217;s a good fit for your test suites and your System Under Test before diving in head-first and investing a lot of time and effort.&lt;/p&gt;
&lt;h2&gt;Addendum 1: Licensing&lt;/h2&gt;
&lt;p&gt;If you want to run multiple instances of QF-Test in parallel, you&amp;#8217;ll need an individual license for every instance, just like when QF-Test is running in a CI/CD environment.&lt;/p&gt;
&lt;p&gt;When launching in batch mode, QF-Test will let you know you if your licenses are insufficient for the number of threads.&lt;/p&gt;
&lt;h2&gt;Addendum 2: Launching Parallel Tests in Daemon Mode&lt;/h2&gt;
&lt;p&gt;Depending on your environment, you may also want to try &lt;a href="https://www.qftest.com/doc/manual/en/user_execdaemon.html"&gt;QF-Test&amp;#8217;s daemon mode&lt;/a&gt; to run multi-threaded tests remotely.&lt;/p&gt;
&lt;p&gt;To start the daemon process, run the following command on the host:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qftest -daemon -daemonport=3544&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then, you could trigger parallel tests on the host via the following Jython server script:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.daemon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DaemonRunContext&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.daemon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DaemonLocator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.util&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Properties&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3544&lt;/span&gt;
&lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;qftest&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;suite.path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;calldaemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DaemonLocator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locateDaemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;trd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createTestRunDaemon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createContexts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UserException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Could not create &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; run contexts. Not enough licenses available?&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# start tests&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;#Run web tests.Test &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bindings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# wait for end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitForRunState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DaemonRunContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STATE_FINISHED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;# Run did not finish, terminate it&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stopRun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rollbackDependencies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitForRunState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DaemonRunContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STATE_FINISHED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;# Context is deadlocked&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;No reply from daemon RunContext.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Daemon call did not terminate and had to be stopped.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getResult&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getRunLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addDaemonLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;calldaemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;testcase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Result from daemon: &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;For more tips and information about parallel load testing, see the user manual chapter &lt;a href="https://www.qftest.com/doc/manual/en/user_loadtest.html"&gt;&amp;#8220;Performing UI-based load tests&amp;#8221;&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Working as a Team with QF-Test – Dos and Don’ts</title><link href="https://www.qftest.com/en/blog/article/working-as-a-team-with-qf-test-dos-and-donts.html" rel="alternate"/><published>2022-02-02T00:00:00+01:00</published><updated>2022-02-02T00:00:00+01:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2022-02-02:/en/blog/article/working-as-a-team-with-qf-test-dos-and-donts.html</id><summary type="html">&lt;p&gt;Sure, a team of testers can handle extensive test projects faster and better than a single person. But some things that may seem simple at first actually can become much more difficult when working in a team.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&amp;#8220;&lt;em&gt;Teamwork: A few harmless flakes working together can unleash an avalanche of destruction.&lt;/em&gt;&amp;#8221; — Justin Sewell&lt;/p&gt;
&lt;p&gt;&lt;img alt="Austausch im Team" src="/images/icons/streamline-icon-team-exchange-chat@48x48-blau.svg" /&gt;&lt;/p&gt;
&lt;p&gt;Sure, a team of testers can handle extensive test projects faster and better than a single person. But some things that may seem simple at first actually can become much more difficult when working in a team.&lt;/p&gt;
&lt;p&gt;Probably every team knows the dreaded &lt;strong&gt;&lt;em&gt;merge conflict&lt;/em&gt;&lt;/strong&gt; that arises when several people have made changes to a file at the same time that contradict each other.&lt;/p&gt;
&lt;p&gt;Resolving such conflicts is time-consuming and error-prone, so it pays to do what you can to avoid them in the first place.&lt;/p&gt;
&lt;p&gt;And even if you don&amp;#8217;t run into a conflict, two commits may still do the same thing in different ways, which is another way to unnecessarily complicate your project.&lt;/p&gt;
&lt;p&gt;During the development of QF-Test, we too racked our brains on how to avoid such conflicts and built some useful tools for that directly into QF-Test. So here come some of the Dos and Don&amp;#8217;ts of teamwork with QF-Test.&lt;/p&gt;
&lt;h2&gt;1. Don&amp;#8217;t Reinvent the Wheel&lt;/h2&gt;
&lt;p&gt;When you first start implementing your test suites, you will come across many small tasks that need to be done. But you don&amp;#8217;t have to reinvent all these things! QF-Test comes with the standard library &lt;code&gt;qfs.qft&lt;/code&gt;, which bundles many frequently needed functions. You need to compare two numbers? &lt;code&gt;qfs.check.compareTwoNumbers&lt;/code&gt; is your friend! Need to interact with an external database? &lt;code&gt;qfs.database.executeStatement&lt;/code&gt; takes the boilerplate code off your hands! Send an email? &lt;code&gt;qfs.utils.email.sendEmail&lt;/code&gt;! Reset browser cookies? &lt;code&gt;qfs.browser.settings.deleteCookies&lt;/code&gt;! We could list many more examples, but you should &lt;a href="https://www.qftest.com/doc/tutorial/en/stdliboverviewwin.html#sec_StandardLibraryOverviewWin"&gt;just browse our library yourself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And if you do need to invent something that&amp;#8217;s missing from &lt;code&gt;qfs.qft&lt;/code&gt;: Start your own standard library! Collect your basic procedures in a common file and keep your team informed about new developments.&lt;/p&gt;
&lt;h2&gt;2. Don&amp;#8217;t Put Everything in One File&lt;/h2&gt;
&lt;p&gt;The number one cause of hairy merge conflicts is simultaneous editing of a file by multiple people. You will also experience this during test development with QF-Test when working in a team on a large &lt;code&gt;.qft&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Our recommendation is therefore to split up larger test suites. In our years of working with clients with test projects of varying sizes, the following basic split has proven effective:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One file with all basic procedures&lt;/li&gt;
&lt;li&gt;One file with all windows and components&lt;/li&gt;
&lt;li&gt;Individual files with test cases and procedures as needed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In order for a test suite to access components or procedures from another file, that file must be listed in the details of the &amp;#8220;Test suite&amp;#8221; node under &amp;#8220;Include Files&amp;#8221; (ideally as a relative path).&lt;/p&gt;
&lt;h2&gt;3. Import Instead of Copy&lt;/h2&gt;
&lt;p&gt;When you split your test suites apart, you may be tempted to simply drag and drop or &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;X&lt;/kbd&gt;, &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;V&lt;/kbd&gt; your procedures and components from one suite to another. The better way is to use the import functionality from the target suite under &amp;#8220;File&amp;#8221; › &amp;#8220;Import&amp;#8230;&amp;#8221;. Now QF-Test will take care of updating all internal references correctly.&lt;/p&gt;
&lt;p&gt;Ideally, you also create new tests this way: Start developing the new test in a completely independent temporary test suite. After finalizing the test, import the individual parts (test cases, components, and procedures) into the corresponding existing test suites. This way, you can conveniently use, for example, the recording functionality of QF-Test without worrying about messing up your global component tree.&lt;/p&gt;
&lt;h2&gt;4. Use QF-Test Projects&lt;/h2&gt;
&lt;p&gt;When you start splitting up your suites, you will notice that QF-Test always tries to keep references to components or procedures in your suites up to date, even if the referenced object has been moved. For this to work smoothly, you should organize your files into a QF-Test &lt;strong&gt;Project&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To do this, select &amp;#8220;File&amp;#8221; › &amp;#8220;New Project&amp;#8221; and navigate to the root directory of your project. The &lt;code&gt;.qpj&lt;/code&gt; file thus created represents your project, to which all files in the same directory and subdirectories belong. QF-Test will monitor the references in all files and automatically update them as needed.&lt;/p&gt;
&lt;p&gt;As a bonus you will get a convenient overview of the files in your project in an additional sidebar in QF-Test whenever you open the associated &lt;code&gt;.qpj&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;5. Talk to Each Other&lt;/h2&gt;
&lt;p&gt;Despite all efforts, no project management method or technical solution can replace the most important component of teamwork: Communication. For joint work to succeed, you need to be in regular conversation with your team members, be it at a regular standup or a random encounter at the coffee machine.&lt;/p&gt;
&lt;p&gt;Clarify responsibilities and plan tasks so that no one gets in another&amp;#8217;s way.&lt;/p&gt;
&lt;p&gt;Only if everyone knows approximately what the other members of the team are doing and planning can they adjust to each other and ensure, for example, that two people are not working on the same test case or even that one team member is reworking part of your test architecture while others are actively working with this architecture. Even the most sophisticated project strategy cannot protect you from such problems.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Is teamwork still giving you a headache? We recently did a video webinar about teamwork with QF-Test. You can find the full-length video recording of it here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/?v=-t1LBjEDitk&amp;amp;list=PLsQ3ggMe67qgEd4HTruyIgzvdq0RYOzBX"&gt;Jan. 2022 &amp;ndash; &lt;strong&gt;Dos and Don&amp;#8217;ts when Working with QF-Test as a Team&lt;/strong&gt; e.g. Version Management (GIT, SVN), Import, Projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gitlab.com/qfs/webinars/-/tree/main/dosAndDonts-working-in-a-team"&gt;Presentation and Testsuite&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>20 years Quality First Software GmbH – Happy Birthday</title><link href="https://www.qftest.com/en/blog/article/20-years-quality-first-software-gmbh.html" rel="alternate"/><published>2021-12-28T00:00:00+01:00</published><updated>2021-12-28T00:00:00+01:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2021-12-28:/en/blog/article/20-years-quality-first-software-gmbh.html</id><summary type="html">&lt;p&gt;Quality First Software GmbH is 20 years old. We asked all colleagues to share their first memories with us.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today 20 years ago, on December 28, 2021, Quality First Software GmbH was founded by Gregor Schmid and Karlheinz Kellerer to replace QFS as a GbR. First, also international customers existed already at that time. When asked about the GmbH foundation date &amp;ndash; why so close to the turn of the year &amp;ndash; Gregor Schmid remembers: &amp;#8220;This was a date request of the notary&amp;#8217;s office and unfortunately resulted in having to prepare a whole GmbH balance sheet 2001 for the 4 days in 2001.&amp;#8221; Such small start-up difficulties provide amusement 20 years later. There was no plan to lead the company QFS in 10 or 20 years to this or that point regarding turnover, number of employees or technology. All the more pleasing that the high flexibility in all areas, also disparagingly called &amp;#8220;business by accident&amp;#8221; by some, has led to a permanent but slow growth with a great dedicated team of 25 people and more than 1,400 customers worldwide.&lt;/p&gt;
&lt;p&gt;&lt;img alt="20 years QFS Birthday cake" src="/blog/resources/qfs-20-years-birthday-cake.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Quotes from colleagues and their first QFS memory&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Wow K. is leaving Siemens and doing Quality First with G., sounds exciting.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;They are doing something with Android and smartphones, I don&amp;#8217;t know if that fits you.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;We use QF-Test for testing from the EAI tool. Check it out and work your way in.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;QFS has home offices.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Hello, would any of you be interested in editing videos for my work as a student/student job?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I have a very nice employee oriented client in Geretsried.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;There might be a company nearby, take a look at the site &amp;ndash; got it from badminton training. These are the ones from the colorful house.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;QFS, a company with a broader horizon&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;If you don&amp;#8217;t want to tinker with computers anymore, but want to program something, there&amp;#8217;s a company where I&amp;#8217;m in sales, they make software to test software. It&amp;#8217;s a fun bunch and there&amp;#8217;s kicker table!&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;My boss allows us 2h per week free development time where you can also just try something out. Actually he would like to allow more, but we are too small a company and there is too much to do&amp;#8230;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;#8230;is a young company that develops software and is quite successful with it; they just bought an (office) house&amp;#8230;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;QFS, the small nice company with super sympathetic people, always worth a visit. The first company that really takes quality seriously and doesn&amp;#8217;t just write it on the website.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Very sympathetic company, I think I could feel really comfortable there.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I waited outside &amp;ndash; because I was too early &amp;ndash; &amp;#8220;you can ring the bell&amp;#8221;.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I have discovered a new TA tool, from a German company, with German-speaking support, and even already got an answer that it is possible to play long clicks!&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I got the most heartwarmingly friendly rejection ever from T. at the time.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Do you really think that QF-Test has hardly any bugs?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;We all have kids and we know how important family life is, that&amp;#8217;s why 2 days a week are home office.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What a &amp;#8220;cool&amp;#8221; idea, that would have definitely saved me a lot of complex JUnit tests.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>“Is FX Even Still Alive?” – JavaFX Adopters Meeting 2021</title><link href="https://www.qftest.com/en/blog/article/is-fx-even-still-alive-javafx-adopters-meeting-2021.html" rel="alternate"/><published>2021-12-22T00:00:00+01:00</published><updated>2021-12-22T00:00:00+01:00</updated><author><name>Gregor Schmid</name></author><id>tag:www.qftest.com,2021-12-22:/en/blog/article/is-fx-even-still-alive-javafx-adopters-meeting-2021.html</id><summary type="html">&lt;p&gt;Is FX even still alive? From QFS&amp;#8221; point of view, we can answer this question with a clear &amp;#8220;yes&amp;#8221;: JavaFX is alive and well and has been in use via QF-Test/FX in multiple large projects since 2014.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="JavaFX Cup" src="/blog/resources/JavaFX.png" /&gt;&lt;/p&gt;
&lt;p&gt;Is FX even still alive? From QFS&amp;#8217; point of view, we can answer this question with a clear &amp;#8220;yes&amp;#8221;: JavaFX is alive and well and has been in use via QF-Test/FX in multiple large projects since 2014.&lt;/p&gt;
&lt;p&gt;The lovely thing is that QF-Test/FX requires little maintenance, and tests of JavaFX are very stable.&lt;/p&gt;
&lt;p&gt;Originally developed by Oracle and made an official part of the JRE with Java 8, JavaFX was removed again with Java 11 and continued as the open source project &lt;a href="https://openjfx.io/"&gt;OpenJFX&lt;/a&gt; - still mainly maintained by Oracle, but in cooperation with &lt;a href="https://gluonhq.com/"&gt;Gluon&lt;/a&gt; and other active participants. On a technical level this move was quite reasonable and OpenJFX is very much alive, as also proved by the JavaFX Adopters Meeting of November 16.&lt;/p&gt;
&lt;p&gt;Invited by Zeiss, the aforementioned players and many other people from Europe interested in JavaFX came together online to share their experiences and ideas around JavaFX. Among them, for example, was &lt;a href="https://www.jpro.one/"&gt;JPro&lt;/a&gt; which brings FX into the web browser &amp;ndash; and of course can be easily tested with QF-Test. For QFS, our own Gregor Schmid and Michael Höber attended.&lt;/p&gt;
&lt;h2&gt;Uncertainty and Criticism&lt;/h2&gt;
&lt;p&gt;So why does the question &amp;#8220;Is JavaFX still alive?&amp;#8221; come up again and again?&lt;/p&gt;
&lt;p&gt;If you search for JavaFX on the internet, you will find some amount of uncertainty and criticism from voices marked by the disastrous marketing behavior on the part of Oracle at that time. The &amp;#8216;net does not forget. At least the tanker Oracle is now slowly moving in a different direction. Nevertheless, JavaFX&amp;#8217;s notoriety caused by this history does not do justice to its outstanding technical quality.&lt;/p&gt;
&lt;p&gt;We at QFS do not experience JavaFX as the long awaited successor of Java Swing. Yes, a part of our customers who migrate their applications away from Swing rely on JavaFX. But a much larger portion is moving toward Web technologies instead. The biggest point in favour of JavaFX is the &amp;#8220;perceived quality&amp;#8221; of projects built with it. &lt;a href="/en/company/references/case-studies/centrisag.html"&gt;Where we accompany JavaFX projects more intensively in testing&lt;/a&gt; we generally see high application design quality and consistently built test architectures.&lt;/p&gt;
&lt;h2&gt;What can we do?&lt;/h2&gt;
&lt;p&gt;So the participants of the JavaFX Adopters Meeting came back to the core question: How can the awareness and popularity of JavaFX be improved? A patent remedy is not emerging. Maybe Oracle will increase its visible efforts again in the foreseeable future &amp;ndash; that would be helpful at any rate, especially since JavaFX (and the associated logo!) is a trademark of Oracle and may not be used freely. Therefore there was also a serious discussion whether the &amp;ndash; less burdened &amp;ndash; name OpenJFX should be emphasized instead. A nice, custom logo would be the urgent first step for this.&lt;/p&gt;
&lt;p&gt;In any case, we would need companies whose core applications are based on JavaFX to actively support this project. They could, for example, commit themselves to communicating their framework preferences more clearly. Or they could contribute to the further development of JavaFX by transferring their own improvements to the OpenJFX project, enter them at &lt;a href="https://www.jfx-central.com/home"&gt;JFX-Central&lt;/a&gt; or &lt;a href="https://www.jfx-ensemble.com"&gt;JFX Ensemble&lt;/a&gt; or support Gluon by signing a maintenance agreement. After all, Gluon manage a significant amount of the development efforts and they are the central point of contact for the binary packages.&lt;/p&gt;
&lt;p&gt;So we do have a chance over the next few years to make JavaFX known to the broader Java developer community for what it could be: A high-quality, modern, mature replacement for Java Swing.&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>No Log4j Vulnerability in QF-Test</title><link href="https://www.qftest.com/en/blog/article/no-log4j-vulnerability-in-qf-test.html" rel="alternate"/><published>2021-12-13T00:00:00+01:00</published><updated>2021-12-13T00:00:00+01:00</updated><author><name>Pascal Bihler</name></author><id>tag:www.qftest.com,2021-12-13:/en/blog/article/no-log4j-vulnerability-in-qf-test.html</id><summary type="html">&lt;p&gt;In the last days, a vulnerability in the popular open source library log4j has been reported, nick-named Log4shell. QF-Test is not (and has not) been vulnerable to this attack, for a number of reasons.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last update: 2022-05-19&lt;/p&gt;
&lt;h2&gt;QF-Test and the QF-Test license server are not vulnerable to Log4shell attack CVE-2021-44228&lt;/h2&gt;
&lt;p&gt;In the last days, &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-44228"&gt;a vulnerability in the popular open source library log4j has been reported, nick-named Log4shell&lt;/a&gt;. &lt;strong&gt;QF-Test is not (and has not) been vulnerable to this attack&lt;/strong&gt;, for a number of reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;QF-Test does not directly use log4j, but has always used its own logging system which does not allow any string interpretation or code evaluation.&lt;/li&gt;
&lt;li&gt;The JREs bundled with QF-Test &amp;ndash; Oracle JDK and OpenJDK &amp;ndash; have shipped with a default setting that should prevent exploitation &lt;a href="https://www.oracle.com/java/technologies/javase/8u121-relnotes.html"&gt;since Oracle JRE 8u121 in 2019&lt;/a&gt;, the variable &lt;code&gt;com.sun.jndi.rmi.object.trustURLCodebase&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;null&lt;/code&gt; by default, disallowing access to remote resources, and it is not changed by QF-Test. Our software currently ships with JRE 8u292.&lt;/li&gt;
&lt;li&gt;Normally QF-Test is not executed in a server mode, so intruders cannot inject any string with malicious content. But also in daemon or license server mode QF-Test does not require any log4j classes.&lt;/li&gt;
&lt;li&gt;None of the third-party libraries shipped with QF-Test contain log4j in any version vulnerable to the log4shell attack*. This has been verified by us as described below for all current and past official QF-Test versions back to QF-Test 3.4.11 which predates the first vulnerable log4j version 2.0beta9.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;*) The log4j classes, which were embedded in the bundled version of the external ui-automation library before QF-Test 6.0.0 are known to not be vulnerable to the CVE-2021-44228 attack.&lt;/p&gt;
&lt;h2&gt;How to verify yourself that no vulnerable version of log4j is in use&lt;/h2&gt;
&lt;p&gt;To verify that no vulnerable version of log4j is bundled with QF-Test, you can use the &lt;a href="https://github.com/mergebase/log4j-detector"&gt;Log4j detector tool&lt;/a&gt;. First, download the &lt;a href="https://raw.githubusercontent.com/mergebase/log4j-detector/b47d906e876f9fe5c7094144d0ddef413a1db061/log4j-detector-2021.12.13.jar"&gt;library&lt;/a&gt; to your system, e.g. to &lt;code&gt;C:\TEMP&lt;/code&gt;. Open a command shell and navigate to the directory where you placed the file in. Then you can use the Java bundled with QF-Test to run the detection tool like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s s-Atom"&gt;#&lt;/span&gt; &lt;span class="nv"&gt;On&lt;/span&gt; &lt;span class="nv"&gt;Windows&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s s-Atom"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;Program&lt;/span&gt; &lt;span class="nv"&gt;Files&lt;/span&gt;&lt;span class="s s-Atom"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;QFS&lt;/span&gt;&lt;span class="s s-Atom"&gt;\qftest\qftest&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;5.3.4&lt;/span&gt;&lt;span class="s s-Atom"&gt;jre\win64\bin\java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s s-Atom"&gt;exe&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; -jar log4j-detector-2021.12.13.jar &amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s s-Atom"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;Program&lt;/span&gt; &lt;span class="nv"&gt;Files&lt;/span&gt;&lt;span class="s s-Atom"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;QFS&lt;/span&gt;&lt;span class="s s-Atom"&gt;\qftest\&lt;/span&gt;&lt;span class="err"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Analyzing&lt;/span&gt; &lt;span class="nf"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;could&lt;/span&gt; &lt;span class="s s-Atom"&gt;take&lt;/span&gt; &lt;span class="s s-Atom"&gt;a&lt;/span&gt; &lt;span class="s s-Atom"&gt;long&lt;/span&gt; &lt;span class="s s-Atom"&gt;time&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s s-Atom"&gt;specify&lt;/span&gt; &lt;span class="s s-Atom"&gt;the&lt;/span&gt; &lt;span class="s s-Atom"&gt;&amp;#39;--verbose&amp;#39;&lt;/span&gt; &lt;span class="s s-Atom"&gt;flag&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="s s-Atom"&gt;have&lt;/span&gt; &lt;span class="s s-Atom"&gt;every&lt;/span&gt; &lt;span class="s s-Atom"&gt;file&lt;/span&gt; &lt;span class="s s-Atom"&gt;examined&lt;/span&gt; &lt;span class="s s-Atom"&gt;printed&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;STDERR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;No&lt;/span&gt; &lt;span class="s s-Atom"&gt;vulnerable&lt;/span&gt; &lt;span class="nv"&gt;Log4J&lt;/span&gt; &lt;span class="mf"&gt;2.&lt;/span&gt;&lt;span class="s s-Atom"&gt;x&lt;/span&gt; &lt;span class="s s-Atom"&gt;samples&lt;/span&gt; &lt;span class="s s-Atom"&gt;found&lt;/span&gt; &lt;span class="s s-Atom"&gt;in&lt;/span&gt; &lt;span class="s s-Atom"&gt;supplied&lt;/span&gt; &lt;span class="s s-Atom"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s s-Atom"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;Program&lt;/span&gt; &lt;span class="nv"&gt;Files&lt;/span&gt;&lt;span class="s s-Atom"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;QFS&lt;/span&gt;&lt;span class="s s-Atom"&gt;\qftest\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Congratulations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;the&lt;/span&gt; &lt;span class="s s-Atom"&gt;supplied&lt;/span&gt; &lt;span class="s s-Atom"&gt;paths&lt;/span&gt; &lt;span class="s s-Atom"&gt;are&lt;/span&gt; &lt;span class="o"&gt;not&lt;/span&gt; &lt;span class="s s-Atom"&gt;vulnerable&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;CVE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;44228&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="s s-Atom"&gt;#&lt;/span&gt; &lt;span class="nv"&gt;On&lt;/span&gt; &lt;span class="nv"&gt;Linux&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;install&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;qftest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;qftest&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;5.3.4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;jre&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;linux64&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;java&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="s s-Atom"&gt;jar&lt;/span&gt; &lt;span class="s s-Atom"&gt;log4j&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="s s-Atom"&gt;detector&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2021.12.13&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s s-Atom"&gt;jar&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;install&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;qftest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Analyzing&lt;/span&gt; &lt;span class="nf"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;could&lt;/span&gt; &lt;span class="s s-Atom"&gt;take&lt;/span&gt; &lt;span class="s s-Atom"&gt;a&lt;/span&gt; &lt;span class="s s-Atom"&gt;long&lt;/span&gt; &lt;span class="s s-Atom"&gt;time&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s s-Atom"&gt;specify&lt;/span&gt; &lt;span class="s s-Atom"&gt;the&lt;/span&gt; &lt;span class="s s-Atom"&gt;&amp;#39;--verbose&amp;#39;&lt;/span&gt; &lt;span class="s s-Atom"&gt;flag&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="s s-Atom"&gt;have&lt;/span&gt; &lt;span class="s s-Atom"&gt;every&lt;/span&gt; &lt;span class="s s-Atom"&gt;file&lt;/span&gt; &lt;span class="s s-Atom"&gt;examined&lt;/span&gt; &lt;span class="s s-Atom"&gt;printed&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;STDERR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;No&lt;/span&gt; &lt;span class="s s-Atom"&gt;vulnerable&lt;/span&gt; &lt;span class="nv"&gt;Log4J&lt;/span&gt; &lt;span class="mf"&gt;2.&lt;/span&gt;&lt;span class="s s-Atom"&gt;x&lt;/span&gt; &lt;span class="s s-Atom"&gt;samples&lt;/span&gt; &lt;span class="s s-Atom"&gt;found&lt;/span&gt; &lt;span class="s s-Atom"&gt;in&lt;/span&gt; &lt;span class="s s-Atom"&gt;supplied&lt;/span&gt; &lt;span class="s s-Atom"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;ins&lt;/span&gt; &lt;span class="s s-Atom"&gt;tall&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;qftest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s s-Atom"&gt;qftest&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="s s-Atom"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Congratulations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;the&lt;/span&gt; &lt;span class="s s-Atom"&gt;supplied&lt;/span&gt; &lt;span class="s s-Atom"&gt;paths&lt;/span&gt; &lt;span class="s s-Atom"&gt;are&lt;/span&gt; &lt;span class="o"&gt;not&lt;/span&gt; &lt;span class="s s-Atom"&gt;vulnerable&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;CVE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;44228&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="s s-Atom"&gt;#&lt;/span&gt; &lt;span class="nv"&gt;On&lt;/span&gt; &lt;span class="s s-Atom"&gt;macOS&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;% /Applications/QF-Test.app/Contents/PlugIns/*.j*/Contents/Home/jre/bin/java -jar log4j-detector-2021.12.13.jar /Applications/QF-Test.app/Contents/Resources&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Analyzing&lt;/span&gt; &lt;span class="nf"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s s-Atom"&gt;could&lt;/span&gt; &lt;span class="s s-Atom"&gt;take&lt;/span&gt; &lt;span class="s s-Atom"&gt;a&lt;/span&gt; &lt;span class="s s-Atom"&gt;long&lt;/span&gt; &lt;span class="s s-Atom"&gt;time&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s s-Atom"&gt;specify&lt;/span&gt; &lt;span class="s s-Atom"&gt;the&lt;/span&gt; &lt;span class="s s-Atom"&gt;&amp;#39;--verbose&amp;#39;&lt;/span&gt; &lt;span class="s s-Atom"&gt;flag&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="s s-Atom"&gt;have&lt;/span&gt; &lt;span class="s s-Atom"&gt;every&lt;/span&gt; &lt;span class="s s-Atom"&gt;file&lt;/span&gt; &lt;span class="s s-Atom"&gt;examined&lt;/span&gt; &lt;span class="s s-Atom"&gt;printed&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;STDERR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;No&lt;/span&gt; &lt;span class="s s-Atom"&gt;vulnerable&lt;/span&gt; &lt;span class="nv"&gt;Log4J&lt;/span&gt; &lt;span class="mf"&gt;2.&lt;/span&gt;&lt;span class="s s-Atom"&gt;x&lt;/span&gt; &lt;span class="s s-Atom"&gt;samples&lt;/span&gt; &lt;span class="s s-Atom"&gt;found&lt;/span&gt; &lt;span class="s s-Atom"&gt;in&lt;/span&gt; &lt;span class="s s-Atom"&gt;supplied&lt;/span&gt; &lt;span class="s s-Atom"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Applications&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;QF&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s s-Atom"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Contents&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="s s-Atom"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;Congratulations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s s-Atom"&gt;the&lt;/span&gt; &lt;span class="s s-Atom"&gt;supplied&lt;/span&gt; &lt;span class="s s-Atom"&gt;paths&lt;/span&gt; &lt;span class="s s-Atom"&gt;are&lt;/span&gt; &lt;span class="o"&gt;not&lt;/span&gt; &lt;span class="s s-Atom"&gt;vulnerable&lt;/span&gt; &lt;span class="s s-Atom"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;CVE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;44228&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(In this example, default paths are used. Please adapt them to the requirements of your system.)&lt;/p&gt;
&lt;h3&gt;Verifying your own plug-ins&lt;/h3&gt;
&lt;p&gt;If your tests rely on external plugins not bundled and shipped with QF-Test you may want to make sure that these plugins are clean, too. From the QF-Test menu, open &amp;#8220;Help-&amp;gt;Info&amp;#8230;&amp;#8221; (on macOS &amp;#8220;QF-Test-&amp;gt;About QF-Test&amp;#8221;), select the &amp;#8220;System info&amp;#8221; tab and follow the link to &amp;#8220;dir.plugin&amp;#8221;. In your shell, execute the command from above, but replace path in the last argument with the path for &amp;#8220;dir.plugin&amp;#8221;, for example:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;macOS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c"&gt;% /Applications/QF-Test.app/Contents/PlugIns/*.j*/Contents/Home/jre/bin/java -jar log4j-detector-2021.12.13.jar &amp;quot;/Users/pascal/Library/Application Support/de.qfs.apps.qftest/plugin&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Testing Excel files</title><link href="https://www.qftest.com/en/blog/article/testing-excel-files.html" rel="alternate"/><published>2021-11-30T00:00:00+01:00</published><updated>2021-11-30T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2021-11-30:/en/blog/article/testing-excel-files.html</id><summary type="html">&lt;p&gt;Excel files can be read into QF-Test using a simple procedure call where the procedure qfs.utils.files.readExcelFile must be called.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://services.qftest.com/en/license/request/"&gt;Free Trial&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Testing Excel files: procedure Call in QF-Test" src="/blog/resources/excel-testen-progCallExcel.png" /&gt;&lt;/p&gt;
&lt;p&gt;The procedure call creates a property variable which contains the different values of the Excel file. Hereby, the name of the property group is passed via the resultGroupName parameter. so, in the above example ${resultGroup:A1} will expand to the value located in the cell A1, ${resultGroup:A2} will expand to the value located in the cell A2 and so on.&lt;/p&gt;
&lt;p&gt;Afterwards, it&amp;#8217;s possible to check the different values of the excel file, for example with the help of the different procedures in the &lt;a href="/include/qfs_pkgdoc/qfs_pkgdoc.html#qfs.check"&gt;check-Package&lt;/a&gt; (e.g. &lt;a href="/include/qfs_pkgdoc/qfs_pkgdoc.html#qfs.check.checkValueToBeEqual"&gt;checkValueToBeEqual&lt;/a&gt;). However, especially if you do have a large Excel file, creating so many procedure calls can be cumbersome. In this case it makes sense to create a reference Excel file. Then QF-Test can read both the Excel file and the reference Excel file into different property groups. The following Jython server script can then be used to compare the property groups with one another:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;p1&lt;/span&gt; = &lt;span class="n"&gt;rc&lt;/span&gt;.&lt;span class="nb"&gt;lookup&lt;/span&gt;(&lt;span class="s"&gt;&amp;quot;groupName1&amp;quot;&lt;/span&gt;)
&lt;span class="n"&gt;p2&lt;/span&gt; = &lt;span class="n"&gt;rc&lt;/span&gt;.&lt;span class="nb"&gt;lookup&lt;/span&gt;(&lt;span class="s"&gt;&amp;quot;groupName2&amp;quot;&lt;/span&gt;)
&lt;span class="n"&gt;props1&lt;/span&gt; = &lt;span class="n"&gt;rc&lt;/span&gt;.&lt;span class="n"&gt;getProperties&lt;/span&gt;(&lt;span class="n"&gt;p1&lt;/span&gt;)
&lt;span class="n"&gt;props2&lt;/span&gt; = &lt;span class="n"&gt;rc&lt;/span&gt;.&lt;span class="n"&gt;getProperties&lt;/span&gt;(&lt;span class="n"&gt;p2&lt;/span&gt;)

&lt;span class="n"&gt;errors&lt;/span&gt; = []

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="nb"&gt;in&lt;/span&gt; &lt;span class="n"&gt;props1&lt;/span&gt;.&lt;span class="nb"&gt;keys&lt;/span&gt;():
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;props2&lt;/span&gt;.&lt;span class="n"&gt;containsKey&lt;/span&gt;(&lt;span class="n"&gt;p&lt;/span&gt;):
        &lt;span class="n"&gt;v1&lt;/span&gt; = &lt;span class="n"&gt;props1&lt;/span&gt;.&lt;span class="nb"&gt;get&lt;/span&gt;(&lt;span class="n"&gt;p&lt;/span&gt;)
        &lt;span class="n"&gt;v2&lt;/span&gt; = &lt;span class="n"&gt;props2&lt;/span&gt;.&lt;span class="nb"&gt;get&lt;/span&gt;(&lt;span class="n"&gt;p&lt;/span&gt;)
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; != &lt;span class="n"&gt;v2:&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;.&lt;span class="nb"&gt;append&lt;/span&gt;(&lt;span class="s"&gt;&amp;quot;%s has value %s in %s but %s in %s&amp;quot;&lt;/span&gt; % (&lt;span class="n"&gt;p&lt;/span&gt;, &lt;span class="n"&gt;v1&lt;/span&gt;, &lt;span class="n"&gt;p1&lt;/span&gt;, &lt;span class="n"&gt;v2&lt;/span&gt;, &lt;span class="n"&gt;p2&lt;/span&gt;))
    &lt;span class="n"&gt;else:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;.&lt;span class="nb"&gt;append&lt;/span&gt;(&lt;span class="s"&gt;&amp;quot;%s in %s but not in %s&amp;quot;&lt;/span&gt; % (&lt;span class="n"&gt;p&lt;/span&gt;, &lt;span class="n"&gt;p1&lt;/span&gt;, &lt;span class="n"&gt;p2&lt;/span&gt;))

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="nb"&gt;in&lt;/span&gt; &lt;span class="n"&gt;props2&lt;/span&gt;.&lt;span class="nb"&gt;keys&lt;/span&gt;():
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;not&lt;/span&gt; &lt;span class="n"&gt;props1&lt;/span&gt;.&lt;span class="n"&gt;containsKey&lt;/span&gt;(&lt;span class="n"&gt;p&lt;/span&gt;):
        &lt;span class="n"&gt;errors&lt;/span&gt;.&lt;span class="nb"&gt;append&lt;/span&gt;(&lt;span class="s"&gt;&amp;quot;%s in %s but not in %s&amp;quot;&lt;/span&gt; % (&lt;span class="n"&gt;p&lt;/span&gt;, &lt;span class="n"&gt;p2&lt;/span&gt;, &lt;span class="n"&gt;p1&lt;/span&gt;))

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors:&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt;.&lt;span class="n"&gt;logError&lt;/span&gt;(&lt;span class="s"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;.&lt;span class="nb"&gt;join&lt;/span&gt;(&lt;span class="n"&gt;errors&lt;/span&gt;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Excel files can be read into QF-Test using a simple &lt;a href="/en/blog/article/how-to-create-procedure-calls.html"&gt;procedure call&lt;/a&gt; where the procedure qfs.utils.files.readExcelFile must be called:&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>How to test browsers like Brave, Vivaldi, Yandex, Iron</title><link href="https://www.qftest.com/en/blog/article/testing-browsers-brave-vivaldi-yandex-with-qf-test.html" rel="alternate"/><published>2021-10-19T00:00:00+02:00</published><updated>2021-10-19T00:00:00+02:00</updated><author><name>Max Melzer</name></author><id>tag:www.qftest.com,2021-10-19:/en/blog/article/testing-browsers-brave-vivaldi-yandex-with-qf-test.html</id><summary type="html">&lt;p&gt;QF-Test natively supports testing websites with Chrome, Firefox, Edge, some others and even good old Internet Explorer. But there are plenty of other, lesser-known browsers that you might still want to run your tests with, such as Chromium, Brave, Iron, Vivaldi and Yandex.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Out of the Box, QF-Test supports &lt;a href="/en/product/qf-test/web-testing.html"&gt;testing Websites&lt;/a&gt; using Chrome, Firefox, Edge, Opera, &lt;a href="https://www.qftest.com/doc/manual/en/user_webtesting.html"&gt;some others&lt;/a&gt;, and even good ol&amp;#8217; Internet Explorer.&lt;/p&gt;
&lt;p&gt;But there are a lot of other, lesser known browsers that you may want to test with anyway, such as (&lt;em&gt;takes deep breath&lt;/em&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the open source &lt;a href="https://www.chromium.org/getting-involved/download-chromium/"&gt;Chromium&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;the crypto-enabled &lt;a href="https://brave.com/"&gt;Brave&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;the privacy-focused &lt;a href="https://www.srware.net/iron/"&gt;Iron&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;the incredibly customizable &lt;a href="https://vivaldi.com/"&gt;Vivaldi&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;or the Russian Firefox-offshoot &lt;a href="https://browser.yandex.com/"&gt;Yandex&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;(&lt;em&gt;phew, did we miss any?&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Less known browsers" src="/blog/resources/browser-logos.png" /&gt;&lt;/p&gt;
&lt;p&gt;Luckily, all of the above (and a &lt;em&gt;lot&lt;/em&gt; of even smaller browsers) are based on the Chromium engine that is behind Google Chrome.&lt;/p&gt;
&lt;p&gt;Because QF-Test understands the &lt;a href="https://chromedevtools.github.io/devtools-protocol/"&gt;CDP&lt;/a&gt; protocol for controlling Google Chrome, it can also work with a lot of the browsers above, but we&amp;#8217;ll need to trick QF-Test a little for it to agree to run those unsupported browsers.&lt;/p&gt;
&lt;p&gt;To run any Chromium-based browser, we need to make QF-Test believe it&amp;#8217;s simply running Chrome. This only takes four edits in our &amp;#8220;Start web engine&amp;#8221; node:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We keep &amp;#8220;chrome&amp;#8221; as our &amp;#8220;Browser type&amp;#8221; of choice to make QF-Test think what we&amp;#8217;re doing is business as usual.&lt;/li&gt;
&lt;li&gt;Under &amp;#8220;Directory of browser installation&amp;#8221;, we sneakily insert the path to our Browser, for example &amp;#8220;C:\Program Files\BraveSoftware\Brave-Browser\Application&amp;#8221; (or &amp;#8220;/Applications/Brave.app&amp;#8221; on macOS).&lt;/li&gt;
&lt;li&gt;To tell QF-Test how the .exe file of the Browser is called (Certainly not &amp;#8220;GoogleChrome.exe&amp;#8221;!), add the following to the &amp;#8220;Executable parameters&amp;#8221; section:&lt;br /&gt;
&lt;code&gt;-Dqftest.web.webdriver.browserFileName=brave.exe&lt;/code&gt; (Or whatever your browser of choice is called). This is not necessary on macOS.&lt;/li&gt;
&lt;li&gt;To make sure your custom browser does not share any settings or caches with your Google Chrome installation, you should override the profile path with a second parameter: &lt;code&gt;-Dqftest.web.profilepath=C:\tmp\brave-profile&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=" QF-Test controlling Brave browser" src="/blog/resources/other-browsers-brave.png" /&gt;&lt;/p&gt;
&lt;p&gt;QF-Test controlling the officially unsupported Brave browser.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Start Web Engine in QF-Test" src="/blog/resources/other-browsers-start-web-engine.png" /&gt;&lt;/p&gt;
&lt;p&gt;Trick QF-Test into running a Chromium-based browser it does not officially support
 Of course, you can still use &lt;code&gt;$(variables)&lt;/code&gt; in pretty much any field of the &amp;#8220;Start web engine&amp;#8221; node. You can easily customize the browser to use and even run the same test suite in a bunch of different browsers using &lt;a href="https://www.qftest.com/doc/manual/en/datadriver.html"&gt;data drivers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This whole thing was an interesting experiment, but in the end you should keep the following in mind: Because these browsers all use the same Chromium engine under the hood, they will generally perform very similarly. In most cases, you will be fine just sticking to the default &amp;#8220;chrome&amp;#8221; Browser type for all your Chromium-based-browser-testing needs.&lt;/p&gt;
&lt;p&gt;Another clear advantage: If something doesn&amp;#8217;t work properly during web testing with one of the officially supported browsers, the QF-Test support team at &lt;a href="mailto:support@qftest.com"&gt;support@qftest.com&lt;/a&gt; will be happy to help.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>TestRunListeners in QF-Test</title><link href="https://www.qftest.com/en/blog/article/testrunlistener-in-qf-test.html" rel="alternate"/><published>2021-08-18T00:00:00+02:00</published><updated>2021-08-18T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2021-08-18:/en/blog/article/testrunlistener-in-qf-test.html</id><summary type="html">&lt;p&gt;The TestRunListener interface can be used to execute additional actions before or after the execution of each node or in the case of any exception / error. This actions can (for example) be used for testdocumentation or error analysis. In the following some TestRunListeners are introduced (Jython server scripts).&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://www.qftest.com/doc/manual/en/tech_testrunlisteners.html#sec_testrunlisteners"&gt;TestRunListener&lt;/a&gt; interface can be used to execute additional actions before or after the execution of each node or in the case of any exception / error.&lt;/p&gt;
&lt;p&gt;This actions can (for example) be used for testdocumentation or error analysis.&lt;/p&gt;
&lt;p&gt;These actions can for example be used for test documentation or error analysis. In the following some TestRunListeners are introduced (Jython server scripts):&lt;/p&gt;
&lt;h2&gt;Variable monitoring&lt;/h2&gt;
&lt;p&gt;The following TestRunListener always logs an error when the value of a certain variable (in the example offset) changes. This is especially helpful when debugging a test suite if it is not clear where exactly the variable is assigned a certain value:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;varname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;offset&amp;quot;&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.extensions.qftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AbstractTestRunListener&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;checkVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Variable &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Variable &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; nicht verfügbar&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;VarChanger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractTestRunListener&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nodeEntered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nodeExited&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;varChanger&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeTestRunListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;varChanger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="n"&gt;varChanger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;VarChanger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addTestRunListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;varChanger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alternatively, it&amp;#8217;s easy to adapt this script so that the TestRunListener always logs an error when a certain value is assigned to the variable.&lt;/p&gt;
&lt;h2&gt;Error sound&lt;/h2&gt;
&lt;p&gt;It can happen that a user starts several tests on several computers at the same time. The following TestRunListener helps if it can happen that the test execution on a machine &amp;ndash; for whatever reason &amp;ndash; can come to a standstill; The TestRunListener always plays a tone if the execution of a node takes longer than a certain specified time limit. It is possible to redefine this time limit for a node using a @informUserTimeOut doctag. (Note: The sound output probably only works on Windows)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# A TestRunListener that informs the user when the current testrun has stopped for more then a certain period of time.&lt;/span&gt;

&lt;span class="c1"&gt;# some imports we need later&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.extensions.qftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AbstractTestRunListener&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.awt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Toolkit&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="c1"&gt;# The default time in ms after which to inform&lt;/span&gt;
&lt;span class="c1"&gt;# the user.&lt;/span&gt;
&lt;span class="n"&gt;TIME_IN_MS_AFTER_WHICH_TO_INFORM_THE_USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;

&lt;span class="c1"&gt;# The function that is used in order to inform the user&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;informUserAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastActionTimestamp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot; This function is called whenever the test-&lt;/span&gt;
&lt;span class="s2"&gt;        runlistener detected the timeout. In oder words,&lt;/span&gt;
&lt;span class="s2"&gt;        this function should play a sound or do something&lt;/span&gt;
&lt;span class="s2"&gt;        else in order to inform the user.&lt;/span&gt;

&lt;span class="s2"&gt;        :lastActionTimestamp: The timestamp of the last action.&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;=== HEY &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; ms passed, since we entered the last node! ===&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lastActionTimestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# output a sound on windows&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Toolkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getDefaultToolkit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getDesktopProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;win.sound.exclamation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# the thread that will inform you once the timeout is reached ...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;InformingThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updateTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TIME_IN_MS_AFTER_WHICH_TO_INFORM_THE_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;updateTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errorAfterTimestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&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="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errorAfterTimestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;informUserAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# the testrunlistener that keeps track of when a new node get&amp;#39;s enetered ...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;InformUserWhenHaveBeenIdleTestRunListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractTestRunListener&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InformingThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;myRegex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@informUserTimeOut&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s+(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d+)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DOTALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nodeEntered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@informUserTimeOut&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;myRegex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&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;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&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="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updateTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TIME_IN_MS_AFTER_WHICH_TO_INFORM_THE_USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;runStopped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="c1"&gt;# register the testrun listener&lt;/span&gt;
&lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;informUserWhenHaveBeenIdleTestRunListener&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;informUserWhenHaveBeenIdleTestRunListener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeTestRunListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;informUserWhenHaveBeenIdleTestRunListener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="n"&gt;informUserWhenHaveBeenIdleTestRunListener&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InformUserWhenHaveBeenIdleTestRunListener&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addTestRunListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;informUserWhenHaveBeenIdleTestRunListener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Counting check nodes&lt;/h2&gt;
&lt;p&gt;The following TestRunListener counts how many check nodes where executed during test execution:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.extensions.qftest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AbstractTestRunListener&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;StatisticTestRunner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractTestRunListener&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkSteps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;runStopped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;steps: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;checkSteps: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkSteps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkSteps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# reset counts&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nodeEntered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Check&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkSteps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;statisticTestRunner&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeTestRunListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statisticTestRunner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="n"&gt;statisticTestRunner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StatisticTestRunner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addTestRunListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statisticTestRunner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;General remarks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Similar to SUT Scripts you should avoid calling rc.callProcedure from within a TestRunListener. Although it&amp;#8217;s often possible to call rc.callProcedure from within a TestRunListener, as this can lead to unexpected problems.&lt;/li&gt;
&lt;li&gt;Instead of derivating from TestRunListener you should always derivate from AbstractTestRunListener.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Creating screenshots with QF-Test</title><link href="https://www.qftest.com/en/blog/article/creating-screenshots-with-qf-test.html" rel="alternate"/><published>2021-07-29T00:00:00+02:00</published><updated>2021-07-29T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2021-07-29:/en/blog/article/creating-screenshots-with-qf-test.html</id><summary type="html">&lt;p&gt;It is relatively easy to take a screenshot of the complete picture with the help of QF-Test. For example, to create a screenshot of the current desktop, only the procedure logScreenshot must be called.&lt;/p&gt;</summary><content type="html">&lt;p&gt;After the execution you can find the screenshot in the corresponding run log.&lt;/p&gt;
&lt;p&gt;&lt;a href="/blog/resources/screenshot-protokollscreenshot-en.png"&gt;Run log Screenshot in QF-Test&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It is also possible to save the screenshot on the hard drive. A Jython Server / SUT script with the following content helps here:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;imagewrapper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;iw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;screenshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grabScreenshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;iw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;savePng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;C:/temp/foo.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It is relatively easy to take a screenshot of the complete picture with the help of QF-Test. For example, to create a screenshot of the current desktop, only the procedure &lt;a href="/include/qfs_pkgdoc/qfs_pkgdoc.html#qfs.run%20log.screenshots.logScreenshot"&gt;logScreenshot&lt;/a&gt; must be called. (The following blog article explains how to insert &lt;a href="/en/blog/article/how-to-create-procedure-calls.html"&gt;a procedure call&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;It should be noted that screenshots can consume a lot of storage space. In this respect, a screenshot should not necessarily be created after each step &amp;ndash; even if this is relatively easy to do. (With the help of a &lt;a href="https://www.qftest.com/doc/manual/en/tech_testrunlisteners.html"&gt;Testrunlistener&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In practice the whole thing looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Screenshot call" src="/blog/resources/screenshot-screenshotcall-en.png" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>JIRA Examples with QF-Test</title><link href="https://www.qftest.com/en/blog/article/jira-examples-with-qf-test.html" rel="alternate"/><published>2021-06-17T00:00:00+02:00</published><updated>2021-06-17T00:00:00+02:00</updated><author><name>Plamen Vesselinov</name></author><id>tag:www.qftest.com,2021-06-17:/en/blog/article/jira-examples-with-qf-test.html</id><summary type="html">&lt;p&gt;On a popular demand we have created examples on how QF-Test can be integrated with JIRA.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Logo Jira Software" src="/blog/resources/Jira-software.png" /&gt;&lt;/p&gt;
&lt;p&gt;On a popular demand we have created examples on how QF-Test can be integrated with JIRA. This is done via the &lt;a href="https://developer.atlassian.com/server/jira/platform/rest/v11001/"&gt;JIRA REST API&lt;/a&gt; The examples are built with the help of the &amp;#8220;HTTP Server request&amp;#8221; node which is the foundation of the &lt;a href="https://www.qftest.com/doc/manual/en/web_services.html#usec_web_services"&gt;Web Service Testing capabilities&lt;/a&gt; in QF-Test.&lt;/p&gt;
&lt;p&gt;The test suite can be found in our public Git repository &lt;a href="https://gitlab.com/qfs/demo-suites/-/tree/master/jira"&gt;https://gitlab.com/qfs/demo-suites/-/tree/master/jira&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You are free to use it and create push requests. In this way you can contribute your generic procedures and help others form the community.&lt;/p&gt;
&lt;h2&gt;Useful info&lt;/h2&gt;
&lt;p&gt;Use a proxy to sniff the traffic while you are building your integration via the &amp;#8220;HTTP Server Request Node&amp;#8221; in QF-Test. More examples for Web Services testing can be found in the demo test suite webservices_testing.qft shipped with QF-Test&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Test automation of KeePass with QF-Test – Seminar paper</title><link href="https://www.qftest.com/en/blog/article/test-automation-of-keepass-with-qf-test-seminar-paper.html" rel="alternate"/><published>2021-04-29T00:00:00+02:00</published><updated>2021-04-29T00:00:00+02:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2021-04-29:/en/blog/article/test-automation-of-keepass-with-qf-test-seminar-paper.html</id><summary type="html">&lt;p&gt;&amp;#8220;Why do my automated UI tests break?&amp;#8221; That was the seminar question of a student. With this, he asked the central question for test automation. Because the success of this, as well as the daily work of QA, stands and falls with the recognition of the objects &amp;ndash; or their failure/breakage of the tests.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&amp;#8220;Why do my automated UI tests break?&amp;#8221; that was the seminar question of a student. With this, he asked &lt;strong&gt;the&lt;/strong&gt; central question for test automation. Because the success of this, as well as the daily work of QA, stands and falls with the recognition of the objects &amp;ndash; or their failure/breakage of the tests.&lt;/p&gt;
&lt;p&gt;His research object was KeePass for Windows in the versions 2.0 to 2.18 (=SUT, System under test). With QF‑Test version he created KeePass2.00 UI tests, which he ran in the following 17 versions.&lt;/p&gt;
&lt;p&gt;&lt;a href="/en/company/references/evaluation-reports/why-do-my-automated-gui-tests-break.html"&gt;To the seminar paper&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="The immediate results of the KeePass UI tests with QF-Test" src="/blog/resources/csm_warum-zerbrechen-gui-tests-figure-2.2_27d4d56a03.png" /&gt;
 The immediate results of the KeePass UI tests with QF-Test
 If tests failed, what was the reason? He found three main reasons (besides single reasons like disappearance of a component) and from all of them he identified the category &amp;#8220;Trivial renaming&amp;#8221; as the main cause.&lt;/p&gt;
&lt;h2&gt;How representative is his result for the experience of the support team of QFS?&lt;/h2&gt;
&lt;p&gt;There it was confirmed to me that &amp;ndash; when tests are finished and running &amp;ndash; equally trivial renaming actions in the further development of the SUT make up the main focus of the requests in the support team. That is, UI tests break quite often because the recognition characteristics of the component have changed.&lt;/p&gt;
&lt;p&gt;So dear developers:&lt;/p&gt;
&lt;h3&gt;Please set unique names or IDs and &lt;strong&gt;never ever&lt;/strong&gt; change them anymore.&lt;/h3&gt;
&lt;p&gt;As written in the seminar paper, changing &amp;#8220;&lt;strong&gt;Lock Windows&lt;/strong&gt;&amp;#8221; to &amp;#8220;&lt;strong&gt;Lock Window&lt;/strong&gt;&amp;#8221; is clearly recognizable as &amp;#8220;identical&amp;#8221; for a developer and human, but a test automaton stumbles over it.&lt;/p&gt;
&lt;p&gt;QF-Test is constantly developing its component detection concepts and optimizing the algorithm to make the generated tests as robust as possible against changes. It offers various possibilities to influence the detection of UI components to make them as stable as possible.&lt;/p&gt;
&lt;p&gt;Currently a new approach is developed, which will make it possible to limit the search area for a component still more dedicated, in order to make so apart from a more stable recognition by exclusion of multiple possible components the test still more stable and beyond that also faster. In addition the maintainability is increased, since one must maintain no more complex component trees.&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>First QF-Test Meetup Switzerland</title><link href="https://www.qftest.com/en/blog/article/first-qf-test-meetup-switzerland.html" rel="alternate"/><published>2021-04-23T00:00:00+02:00</published><updated>2021-04-23T00:00:00+02:00</updated><author><name>Beáta Pázmány</name></author><id>tag:www.qftest.com,2021-04-23:/en/blog/article/first-qf-test-meetup-switzerland.html</id><summary type="html">&lt;p&gt;First Online QF-Test Meetup with users from Switzerland and three QFS team members.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Choo choo! Hop on board on the train to Switzerland!&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Flag Switzerland" src="/blog/resources/flagge-schweiz.png" /&gt;&lt;/p&gt;
&lt;p&gt;Again and again customers are reporting back to us about their interesting solutions with QF-Test – in support, in training or as an advisor on location. We often get the question: &amp;#8220;How do other customers do it? What problems have they encountered? What solutions have they found?&amp;#8221; This resulted in the desire on our part to be able to show something, and in a desire for our customers to be able to see alternate solutions.&lt;/p&gt;
&lt;p&gt;For a while we’ve had the idea to bring together the growing clientele from Switzerland. It was supposed to happen last year, but the COVID-pandemic had us looking for an alternative.&lt;/p&gt;
&lt;p&gt;The idea just hasn’t left us alone – we wanted to go to the mountains so we planned the hike and invited the other travelers to an online meeting.&lt;/p&gt;
&lt;p&gt;The time has finally come last Wednesday, April 14th 2021. Backpacks packed, we could start the online session five minutes before start. As punctual as a Swiss watch, all the participants were there at 5:30 &amp;ndash; 13 QF-Test users and project managers from various companies. Three of us were there to represent QFS: Helena Aschenbrenner (sales) – thank you for organizing, Thomas Max (development) moderated the meetup, and me (testing) has the pleasure to write this blog post.&lt;/p&gt;
&lt;p&gt;After a long, but interesting introduction round we got to see two presentations and get a look at development and testing processes, infrastructure, and testing architecture. Incredible how many possibilities of application there are for QF-Test – like a Swiss army knife! How many testing cases are executed daily? How are tests started? Both presentations focused more on the testing strategies within the projects, as well as the place and role of test automations, rather than the technical implementation of the individual test cases. It was interesting to hear that test automation is being accepted more and more, and what lead to this. We have contributed to the evening as well, with Thomas presenting QF-Test’s newest feature, which is still in the development phase.&lt;/p&gt;
&lt;p&gt;The meetup ended with an open Q&amp;amp;A and discussion round. We are planning to do meetings like this regularly in the future – several participants volunteered to do a short presentation next time. With many of our customers working in the Syrius environment and implementing complex frameworks for this, there is going to be a detailed presentation about that topic as well. This meetup will also serve as an exchange of experiences between all participants and can help optimizing their specific solutions.&lt;/p&gt;
&lt;p&gt;And this is how three QFS team members from three locations in Germany traveled to Switzerland at the same time and came back home less than two hours later. I’m already looking forward to our next travel! Who wants to come along?&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>Against the Corona-Blues</title><link href="https://www.qftest.com/en/blog/article/against-the-corona-blues.html" rel="alternate"/><published>2021-03-02T00:00:00+01:00</published><updated>2021-03-02T00:00:00+01:00</updated><author><name>Gregor Schmid</name></author><id>tag:www.qftest.com,2021-03-02:/en/blog/article/against-the-corona-blues.html</id><summary type="html">&lt;p&gt;A testimonial by &amp;#8220;Mr. QF-Test&amp;#8221; who recently started to place a walking pad under his desk so that his work on QF-Test is now done walking and he is so happy with it that he even volunteered to write a blog about it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;For many, especially those in IT, the past year was probably similar to ours in many ways:&lt;/p&gt;
&lt;p&gt;Since March 2020 our offices have been closed. Except for the occasional visit to take care of the plants or paperwork, all of us are working from home. We were lucky &amp;ndash; thanks to our regular 2 days per week home-office there were no technical problems and the infrastructure providers did their job remarkably well. Basically everything works, some online meetings are even more efficient and personal than before and the market for our niche is stable enough for us to get by well.&lt;/p&gt;
&lt;p&gt;Still, it&amp;#8217;s not always easy to maintain high spirits and the longer the lock-down continues the more deeply the Corona-Blues takes hold. My own content and physical well-being were also on the decline due to lack of opportunity and motivation for exercise and sports. But then a stroke of luck changed things and gave me a push in a direction that is promising to thoroughly change my work-life: In a video-meeting a colleague was happily tottering away. He&amp;#8217;d gotten a treadmill and placed it under his adjustable desk.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gregor Schmid on walking treadmill" src="/blog/resources/csm_2021-gehband-gregor_393841031b.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Printed on the shirt: So that your software runs well.&lt;/p&gt;
&lt;p&gt;Of course! I&amp;#8217;d toyed with that idea years before but unfortunately not gone through with it at that time, partly due to lack of suitable choices. But now I understood immediately: This is it! I already had a suitable desk so I did a quick market research, read and watched a lot of user stories, compared prices, ordered a treadmill, got it delivered, set it up and started walking.&lt;/p&gt;
&lt;p&gt;Today&amp;#8217;s generation of treadmills for walking, not running, is ideally suited for that purpose. The maximum speed is limited to 6 km/h &amp;ndash; not enough for running but more than enough at a desk. With prices below € 400.- the devices are smaller, lighter and less noisy than their large running counterparts in fitness studios and energy consumption is far less.&lt;/p&gt;
&lt;p&gt;Familiarization didn&amp;#8217;t take long. At a relaxed pace of 4 km/h (5 with a bit of practice), typing and mouse operation are not an issue as long as I don&amp;#8217;t need to hit exact pixels. My concentration at work is so high that I barely notice walking. The light tension resulting from this contrast even helps focusing and the increased blood flow stimulates thought. Sitting has its well-known drawbacks and standing is not much better &amp;ndash; I can stand comfortably for maybe half an hour at most. But walking feels like there&amp;#8217;s no limit. After a month the magic still isn&amp;#8217;t gone and I&amp;#8217;m walking about 80-100 km per week without much effort. And that&amp;#8217;s on top, while doing my work, without having to cut out the time and not in competition with going for a run outside in this wonderfully warm spring. And so far without wear and tear.&lt;/p&gt;
&lt;p&gt;Though this approach is not for everyone and not every workplace is suited, the health potential is tremendous. One colleague also started walking, another mounted his bike below his desk. The others are getting used to the waggling in video meetings and, thanks to headsets, acoustics are OK though even the better treadmills aren&amp;#8217;t really silent and there&amp;#8217;s the occasional heavy breath coming through the microphone.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m curious to find out what the next months have in store for us. How quickly or slowly &amp;#8220;normalcy&amp;#8221; returns and what that&amp;#8217;s going to mean for social and work interactions, what new balances we&amp;#8217;re going to find. But what I&amp;#8217;m definitely going to retain after Corona is working while walking (or is it walking while working?) and I&amp;#8217;m totally thankful for that.&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Automated website audits with Lighthouse and QF-Test</title><link href="https://www.qftest.com/en/blog/article/automated-website-audits-lighthouse-qf-test.html" rel="alternate"/><published>2021-02-18T00:00:00+01:00</published><updated>2021-02-18T00:00:00+01:00</updated><author><name>Niklas Wilhelm</name></author><id>tag:www.qftest.com,2021-02-18:/en/blog/article/automated-website-audits-lighthouse-qf-test.html</id><summary type="html">&lt;p&gt;This article shows how to perform automated audits by use of Lighthouse and QF-Test e.g., to measure performance or SEO behavior of your website.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;What is Lighthouse?&lt;/h2&gt;
&lt;p&gt;Lighthouse, an open-source tool which was developed by Google, makes it possible to analyze your website in the four categories performance, accessibility, best practices and SEO. To analyze each category, Lighthouse runs a barrage of tests called audits. Depending on the results of the audits, the respective category will get a score between 0 and 100 (see Figure 1).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Figure 1: Lighthouse Scores" src="/blog/resources/lighthouse-01-scores.jpg" title="Lighthouse Scores" /&gt;
 Figure 1: Lighthouse Scores
 The performance category analyzes how quickly a website loads and how quickly users can access or view the content. The corresponding score will be influenced by metrics like &lt;em&gt;First Contentful Paint&lt;/em&gt; or &lt;em&gt;Time to Interactive&lt;/em&gt;. &lt;em&gt;First Contentful Paint&lt;/em&gt; indicates the time before the first text or image becomes visible to users while &lt;em&gt;Time to Interactive&lt;/em&gt; measures how long it takes a page to become fully interactive.&lt;/p&gt;
&lt;p&gt;Accessibility ensures that people with disabilities can access content and navigate the site effectively. One audit used to determine this category&amp;#8217;s score is checking whether images have been assigned an &lt;code&gt;alt&lt;/code&gt; attribute. They allow reader programs to describe the image&amp;#8217;s content for visually impaired people.&lt;/p&gt;
&lt;p&gt;The third category makes sure that your site follows standard best practices for the web. For example, does it avoid deprecated APIs and does it only embed resources from secure sources?&lt;/p&gt;
&lt;p&gt;Finally, SEO checks how well your page is optimized for search engine result rankings.&lt;/p&gt;
&lt;h2&gt;Integrate Lighthouse audits in QF-Test&lt;/h2&gt;
&lt;p&gt;The procedure allows you to define a warning and/or error limit between 0 and 100 for each of the four categories. For example, if the performance score is below the error limit you specify, QF-Test will alert you (see Figure 2). By setting the value of a limit parameter to -, you can also disable its checking.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Figure 2: Performance score fell below your error limit" src="/blog/resources/lighthouse-02-performance-score-limit.png" title="Performance score fell below your error limit" /&gt;&lt;/p&gt;
&lt;p&gt;Figure 2: Performance score fell below your error limit
 The attached test suite &lt;strong&gt;&lt;a href="https://gitlab.com/qfs/demo-suites/-/blob/master/web/lighthouse.qft"&gt;&lt;strong&gt;lighthouse.qft&lt;/strong&gt;&lt;/a&gt;&lt;/strong&gt; contains a procedure &amp;#8220;checkLighthouseScores&amp;#8221; to start lighthouse audits and check its results. With the help of the procedure&amp;#8217;s parameters, you can define a warning and/or error limit for each of the four categories (Value must be between 0 and 100). If, let&amp;#8217;s say, the performance score falls below your specified error limit, QF-Test will inform you with the respective error message (See Figure 2). To deactivate the check of a limit parameter, you just need to set its value to &lt;strong&gt;-&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;By integrating Lighthouse audits into your automated testing, your site can be continuously checked for unexpected changes. Besides a summary that will be saved in the run log, the procedure also provides several optional parameters for further configuration. For example, the parameter &amp;#8220;mobileEmulation&amp;#8221; allows you to analyze your website not only in a desktop but also a smartphone environment.&lt;/p&gt;
&lt;h2&gt;How to install Lighthouse&lt;/h2&gt;
&lt;p&gt;The first step is to install the Google Chrome Browser, as Lighthouse analyzes your website using its headless mode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Please install Google Chrome. The latest version can be downloaded from: &lt;a href="https://www.google.com/chrome/"&gt;https://www.google.com/chrome&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To be able to execute the procedure, Lighthouse needs to be installed as a Node.js module.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install the current long-term support version (LTS) of Node.js. The latest version can be downloaded from: &lt;a href="https://nodejs.org/en/"&gt;https://nodejs.org/de&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;After Node.js is installed, please open the command line of your operating system (Windows: command prompt).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the opened command line to execute the command shown in figure 3. If Lighthouse should not be installed as a global module, do not use the &lt;strong&gt;-g&lt;/strong&gt; flag.
&lt;img alt="Figure 3: Install Lighthouse" src="/blog/resources/lighhouse-03-install.png" title="Install Lighthouse" /&gt;
 Figure 3: Install Lighthouse&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To ensure correct installation, execute the command &lt;strong&gt;lighthouse&lt;/strong&gt; in the already opened command line. Following output should appear:
&lt;img alt="Figure 4: Lighthouse Output" src="/blog/resources/lighhouse-03-output.png" title="Lighthouse Output" /&gt;
 Figure 4: Lighthouse Output
 For further information regarding Lighthouse please visit: &lt;a href="https://developer.chrome.com/docs/lighthouse/overview/"&gt;Lighthouse&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Final hints&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;In case the lighthouse command cannot be found anyway, a missing entry in your path environment variable may be missing. On Windows you may add e.g. %appdata%\npm by hand.&lt;/li&gt;
&lt;li&gt;After installing npm and lighthouse, QF-Test need to be restarted in order to get aware of changes in the envrionment settings.&lt;/li&gt;
&lt;li&gt;Some lighthouse results are dependent on the network connection and machine performance, the audits are executed on. There should be no constraints or your need to take them into account respectively.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Change language in QF-Test</title><link href="https://www.qftest.com/en/blog/article/language-settings-qf-test.html" rel="alternate"/><published>2021-02-01T00:00:00+01:00</published><updated>2021-02-01T00:00:00+01:00</updated><author><name>Quality First Software</name></author><id>tag:www.qftest.com,2021-02-01:/en/blog/article/language-settings-qf-test.html</id><summary type="html">&lt;p&gt;QF-Test exists in German and English. You can switch the language in QF-Test. It is easy you just need to know how to change the language settings.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;&lt;a href="/en/blog/article/switching-languages-in-qf-test.html"&gt;Notice: An improved version of this post can be found here: Switching languages in QF-Test.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;QF-Test exists in German and English. You can switch the language in QF-Test. It is easy you just need to know how to change the language settings.&lt;/p&gt;
&lt;h2&gt;1. Find qfconfig.exe&lt;/h2&gt;
&lt;p&gt;a. Click your way to your QF-Test installation up to &lt;strong&gt;\bin&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;b. Or right click on the the QF-Test file association on your desktop and then click on &amp;#8220;Open file location&amp;#8221;&lt;/p&gt;
&lt;p&gt;e.g. C:\Program Files\QFS\QF-Test\qftest-5.2.1\bin&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test file association" src="/blog/resources/language-switching-1.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test &amp;quot;Open file location&amp;quot;" src="/blog/resources/language-switching-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;c. Or search for QF-Test in the Windows menu and click directly on &amp;#8220;QF-Test Java configuration&amp;#8221; (Continue with 3.)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Windows menu &amp;quot;QF-Test Java configuration&amp;quot;" src="/blog/resources/language-switching-6.png" /&gt;&lt;/p&gt;
&lt;h2&gt;2. Open qfconfig.exe&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Find QF-Test qfconfig.exe " src="/blog/resources/language-switching-3.png" /&gt;&lt;/p&gt;
&lt;h2&gt;3. Select your required language&lt;/h2&gt;
&lt;p&gt;&lt;img alt="QF-Test select language" src="/blog/resources/language-switching-4.png" /&gt;&lt;/p&gt;
&lt;h2&gt;4. Click &amp;#8220;OK&amp;#8221; and then you’re done.&lt;/h2&gt;
&lt;p&gt;&lt;img alt="QF-Test click &amp;quot;ok&amp;quot;" src="/blog/resources/language-switching-5.png" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Logging the Chrome development console</title><link href="https://www.qftest.com/en/blog/article/logging-chrome-development-console.html" rel="alternate"/><published>2021-01-15T00:00:00+01:00</published><updated>2021-01-15T00:00:00+01:00</updated><author><name>Plamen Vesselinov</name></author><id>tag:www.qftest.com,2021-01-15:/en/blog/article/logging-chrome-development-console.html</id><summary type="html">&lt;p&gt;Loading a web page in the browser and working with it may produce various errors. To log those errors you may use the linked test suite.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Loading a web page in the browser and working with it may produce various errors. To log those errors you may use the linked test suite. The suite is created to be used with Chrome via the WebDriver Connection Mode of QF-Test: &lt;a href="https://gitlab.com/qfs/demo-suites/-/blob/master/web/Chrome_logging.qft"&gt;Chrome_Logging.qft&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you implement a similar logging for other browsers, please let me know at &lt;a href="mailto:support@qftest.com"&gt;support@qftest.com&lt;/a&gt;, I will gladly publish it here.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Public GIT repository for test suites</title><link href="https://www.qftest.com/en/blog/article/public-git-repository-for-test-suites.html" rel="alternate"/><published>2021-01-11T00:00:00+01:00</published><updated>2021-01-11T00:00:00+01:00</updated><author><name>Plamen Vesselinov</name></author><id>tag:www.qftest.com,2021-01-11:/en/blog/article/public-git-repository-for-test-suites.html</id><summary type="html">&lt;p&gt;We are happy to announce the public GIT repository for test suites.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="GIT repository" src="/images/icons/streamline-icon-folder-share@150x150.svg" title="GIT repository" /&gt;&lt;/p&gt;
&lt;p&gt;Dear readers and users of the QF-Test Blog,&lt;/p&gt;
&lt;p&gt;We have a New Year present for you. We are happy to announce the public GIT repository for test suites:  &lt;/p&gt;
&lt;p&gt;&lt;a href="https://gitlab.com/qfs/demo-suites"&gt;https://gitlab.com/qfs/demo-suites&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;Here you will find test suites containing generic procedures for different use-cases which we gather from our users in different occasions. As an example, there are two suites already JIRA.qft and web.qft. Feel free to dive in and bravely explore them!&lt;/p&gt;
&lt;p&gt;Most importantly :  &lt;/p&gt;
&lt;p&gt;YOU can contribute to the repository, push new test suites, procedures, and comment. All changes will be carefully reviewed, but keep in mind not to commit sensitive information.&lt;/p&gt;</content><category term="blog"/><category term="company"/><category term="testing"/></entry><entry><title>Parsing JSON</title><link href="https://www.qftest.com/en/blog/article/parsing-json.html" rel="alternate"/><published>2020-12-07T00:00:00+01:00</published><updated>2020-12-07T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2020-12-07:/en/blog/article/parsing-json.html</id><summary type="html">&lt;p&gt;Especially when testing websites, you are now and then faced with the problem of having to parse JSON (=JavaScript Object Notation) strings.  In this blog article, however, the JSON library provided by QF-Test should be described.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="JSON file" src="/blog/resources/json-file.png" /&gt;&lt;/p&gt;
&lt;p&gt;Especially when testing websites, you are now and then faced with the problem of having to parse JSON (=JavaScript Object Notation) strings.&lt;/p&gt;
&lt;p&gt;Often, for this, a JSON library is downloaded and placed into the corresponding plugin directory. After a restart it is then possible to e.g. use the &lt;a href="https://docs.python.org/3/library/json.html"&gt;Python JSON library&lt;/a&gt; in Jython scripts.&lt;/p&gt;
&lt;p&gt;In this blog article, however, the JSON library provided by QF-Test should be described. This library is based on the &lt;a href="https://github.com/ralfstx/minimal-json"&gt;minimal-json-library&lt;/a&gt; and has the advantage that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it is already supplied with QF-Test (so not a plugin first must be installed)&lt;/li&gt;
&lt;li&gt;It is available in both Jython and Groovy scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following chapters describe how this library can be used for JSON parsing and JSON generation.&lt;/p&gt;
&lt;h2&gt;JSON parsing&lt;/h2&gt;
&lt;p&gt;The general code to parse a JSON string using this library looks like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;
&lt;span class="n"&gt;jsonStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;{&amp;quot;a&amp;quot; : 1, &amp;quot;b&amp;quot; : &amp;quot;zzz&amp;quot;, &amp;quot;c&amp;quot; : [1,2,3]}&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonStr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(Jython Code to parse a Jython String)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.json.Json&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;jsonStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;{&amp;quot;a&amp;quot; : 1, &amp;quot;b&amp;quot; : &amp;quot;zzz&amp;quot;, &amp;quot;c&amp;quot; : [1,2,3]}&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonStr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(Groovy Code to parse a Jython String)&lt;/p&gt;
&lt;p&gt;Afterwards, you can easily access the different values defined in the JSON string:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qf.println(json[&amp;quot;a&amp;quot;])           # Output: 1
qf.println(json[&amp;quot;b&amp;quot;])           # Output: zzz
qf.println(json[&amp;quot;c&amp;quot;])           # Output: [1,2,3]
qf.println(json[&amp;quot;c&amp;quot;][0])        # Output: 1
qf.println(json[&amp;quot;c&amp;quot;][1])        # Output: 2
qf.println(json[&amp;quot;c&amp;quot;][2])        # Output: 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(Jython Code in order to access the various values / output them on the terminal)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qf.println(json[&amp;quot;a&amp;quot;])           // Output: 1
qf.println(json[&amp;quot;b&amp;quot;])           // Output: zzz
qf.println(json[&amp;quot;c&amp;quot;])           // Output: [1,2,3]
qf.println(json[&amp;quot;c&amp;quot;][0])        // Output: 1
qf.println(json[&amp;quot;c&amp;quot;][1])        // Output: 2
qf.println(json[&amp;quot;c&amp;quot;][2])        // Output: 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(Groovy Code in order to access the various values / output them on the terminal)&lt;/p&gt;
&lt;p&gt;In Groovy, the following notation is also possible for JSON Maps:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qf.println(json.a) // Output: 1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;(Groovy Code in order to access the various values / output them on the terminal)&lt;/p&gt;
&lt;p&gt;which makes it possible to &amp;#8220;save&amp;#8221; further characters in the script. In this context let me also point to Groovy&amp;#8217;s &lt;a href="https://stackoverflow.com/questions/24186387/what-is-the-purpose-of-question-mark-after-a-variable-in-groovy"&gt;?-Notation&lt;/a&gt; which is helpful when a certain value can be present in the JSON string but does not have to be present.&lt;/p&gt;
&lt;h2&gt;Generating JSON&lt;/h2&gt;
&lt;p&gt;The general code to generate a JSON string using this library looks like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JsonBuilder&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="s2"&gt;&amp;quot;a&amp;quot;&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="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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&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="n"&gt;jsonStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JsonBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toJsonValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Jython&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.json.JsonBuilder&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;obj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="s2"&gt;&amp;quot;a&amp;quot;&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="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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;jsonStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JsonBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toJsonValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Groovy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Covid-19 and home office at QFS</title><link href="https://www.qftest.com/en/blog/article/covid-19-home-office-qfs.html" rel="alternate"/><published>2020-11-12T00:00:00+01:00</published><updated>2020-11-12T00:00:00+01:00</updated><author><name>Mike Schmidt</name></author><id>tag:www.qftest.com,2020-11-12:/en/blog/article/covid-19-home-office-qfs.html</id><summary type="html">&lt;p&gt;One of our colleagues tells about his experiences with Covid-19 and home office here at Quality First Software Gmbh in 2020.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Covid-19 and home office for three quarters of a year. How has this influenced my (working) life?&lt;/h2&gt;
&lt;p&gt;When we were asked at the beginning of March 2020 to work from home every 5 days during the coming week instead of the usual 2 days, nobody had guessed what would happen after that.&lt;/p&gt;
&lt;p&gt;The idea was only to avoid a possible risk of infection. Then the first exit restriction of the government came. How the further rules and recommendations of the government and the health ministry were appearing … Well, we all know this already.&lt;/p&gt;
&lt;p&gt;Technical no issue. We all had (and have) a working place at home anyway, at which we always (at least in the more than 10 years I am working at QFS) were working at two days a week. This changed to five days, but the technique is the same.&lt;/p&gt;
&lt;p&gt;In the beginning it was quite pleasant to simply save some time and fuel as the work way was eliminated. But I quickly have realized what I was missing. The social contact to the colleagues could now no longer take place face-to-face in the hallway. Just simply to go to the office next door and exchange some information &amp;#8230; that was no longer possible.&lt;/p&gt;
&lt;p&gt;Also, the common table soccer game towards the end of the working days in the office was dropped. Surprisingly, I miss that much less than the sometimes-accidental encounters in the hallway. I would not have thought so if someone had asked me beforehand.&lt;/p&gt;
&lt;p&gt;In retrospect, QFS reacted great, both from the management and from the colleagues. Without the need for any agreements, the stand-up meetings were used more for private exchange (via video conferencing) than for their actual purpose. Even though we talk more about our work tasks of the day or week at the standup meetings nowadays, the personal and human aspect has remained ingrained and has become indispensable.&lt;/p&gt;
&lt;p&gt;In addition, we also have now, mainly driven by our espresso machine expert, simple meetings / videoconferences where we meet on various topics that have nothing to do with QFS. It is often about making coffee, which is why the colleague puts his notebook in his private kitchen in front of his espresso machine. But not only. We simply want to have a human exchange and compensate for the lack of personal contacts.&lt;/p&gt;
&lt;p&gt;In the meantime, we have already arrived in November 2020 and working from home has become part of our daily life. But not a day goes by without talking to at least one colleague. Of course, it is all about work. But we also try to do it mostly via video chat. A phone call would also be sufficient in most cases. But to see the other person and to be seen yourself &amp;#8230; That keeps up the friendly relationship to the colleagues and helps you (at least me) not to succumb to a &amp;#8220;Corona-tantrum&amp;#8221;, because of only sitting at home and seeing your own four walls / your flat from inside only.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Covid-19-Homeoffice Quality First Software" src="/blog/resources/covid-19-homeoffice.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;This situation helps me a lot to recognize what is important to me and what is less. Also, the experience that I am employed by a company where working together plays a big role is once again confirmed in the current situation.&lt;/p&gt;
&lt;p&gt;Thus, I use the situation to learn a lot about myself and in this respect, I see it quite positively. In general, I have become more modest, but also more aware of my wishes.&lt;/p&gt;
&lt;p&gt;Nevertheless:&lt;br /&gt;
 Of course, I am slowly getting fed up with the inevitable phase of reflection and I would love to have social contacts again without a screen in between. Or just visit my father again.&lt;/p&gt;
&lt;p&gt;But that is also a positive insight, I think.&lt;/p&gt;
&lt;p&gt;How do you feel about it? Have you had similar or maybe completely different experiences?&lt;/p&gt;
&lt;p&gt;Just write it in the comments. I am curious&amp;#8230;&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Node Transformations in QF-Test</title><link href="https://www.qftest.com/en/blog/article/node-transformations-qf-test.html" rel="alternate"/><published>2020-11-03T00:00:00+01:00</published><updated>2020-11-03T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2020-11-03:/en/blog/article/node-transformations-qf-test.html</id><summary type="html">&lt;p&gt;QF-Test it is possible to transform a node into another node type.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In QF-Test it is possible to transform a node into another node.&lt;/p&gt;
&lt;p&gt;This feature can be useful when&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a certain action cannot be recorded, for example &amp;#8220;Wait for component&amp;#8221;&lt;/li&gt;
&lt;li&gt;you want to insert actions similar to a recorded one: copy and convert can then be easier as performing a new recording.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To this end, right click on the node to be converted&lt;/p&gt;
&lt;p&gt;&lt;img alt="Node in QF-Test Mouse click" src="/blog/resources/nodeConversions_en.png" /&gt;&lt;/p&gt;
&lt;p&gt;then select &amp;#8220;Transform node into&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Node options in QF-Test" src="/blog/resources/nodeConversions2_en.png" /&gt;&lt;/p&gt;
&lt;p&gt;and choose the desired target node:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Options &amp;quot;Transform node into&amp;quot; in QF-Test" src="/blog/resources/nodeConversions3_en.png" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>The comment node in QF-Test</title><link href="https://www.qftest.com/en/blog/article/comment-node-qf-test.html" rel="alternate"/><published>2020-10-28T00:00:00+01:00</published><updated>2020-10-28T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2020-10-28:/en/blog/article/comment-node-qf-test.html</id><summary type="html">&lt;p&gt;Since QF-Test 5.0.0 a new node was added, here you will learn how to use the comment node.&lt;/p&gt;</summary><content type="html">&lt;p&gt;This node can be inserted everywhere and has (normally) no influence on test execution but can be used exclusively for documentation:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Comment node in QF-Test Example" src="/blog/resources/commentNodeUsage_en.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Highlighting&lt;/h2&gt;
&lt;p&gt;In order to ensure better visibility and legibility of comments nodes, it is possible to highlight a comment (or parts of a comment) by using emphasis (i.e. bold, italic, &amp;#8230;) or by using a specific text color:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Comment node in QF-Test colored" src="/blog/resources/commentNodeColor_en.png" /&gt;&lt;/p&gt;
&lt;p&gt;The corresponding HTML tags can be used to achieve such a highlight. Doing this, a starting tag, indicates that from here on a certain font and / or color should get used. This font and / or color is then retained until a corresponding, so-called end tag appears.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Normal text &amp;lt;b&amp;gt;bold text&amp;lt;/b&amp;gt; normal text again&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Hereby &lt;b&gt; indicates that from here on the text should be marked in bold. The following &lt;/b&gt; cancel the bold emphasis, so that only the middle part of the text is displayed in bold.&lt;/p&gt;
&lt;p&gt;Support for the following tags has been implemented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;i&gt; to indicate that the text should be shown in italics from here on, &lt;/i&gt; as end-tag&lt;/li&gt;
&lt;li&gt;&lt;u&gt; to indicate that the text should get underlined, &lt;/u&gt; as end-tag&lt;/li&gt;
&lt;li&gt;&lt;b&gt; to indicate that the text should be shown in bold from here on, &lt;/b&gt; as end-tag&lt;/li&gt;
&lt;li&gt;&lt;s&gt; that the text should be crossed out, &lt;/s&gt; as end-tag&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For colored markings it is possible to add a color=&amp;#8221;colorname&amp;#8221;-attribute to these tags. That means for a bold, blue text the whole thing has to be changed as follows:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;b color="blue"&amp;gt;bold, blue text&amp;lt;/b&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Whereby you shouldn&amp;#8217;t make the mistake of repeating the color attribute with the end tag (this means to write &lt;/b color="blue"&gt;), even though &lt;/b&gt; will also cancel that later text will be displayed in blue.&lt;/p&gt;
&lt;p&gt;In order to get get a colored text without emphasis, the &lt;font&gt;-tag can be used. So:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;font color="green"&amp;gt;green text&amp;lt;/font&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Comment nodes are written to the run log and are visible for TestRunListener, too.&lt;/li&gt;
&lt;li&gt;Even if it wasn&amp;#8217;t explicitly forbidden &amp;ndash; It only makes limited sense to insert a comment node starting with &amp;#8221; → &amp;#8220;. With the help of &amp;#8221; → &amp;#8221; QF-Test is indicating the step that just gets executed. Thus a comment step starting with &amp;#8221; → &amp;#8221; may confuse users as they may think that this is the step that currently gets executed.
Since QF-Test 5.0.0 a new node was added, the comment node:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Comment node in QF-Test" src="/blog/resources/commentNode_en.png" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Image checks in a world of Font Antialiasing and ClearType</title><link href="https://www.qftest.com/en/blog/article/image-checks-in-a-world-of-font-antialiasing-and-cleartype.html" rel="alternate"/><published>2020-08-03T00:00:00+02:00</published><updated>2020-08-03T00:00:00+02:00</updated><author><name>Michael Höber</name></author><id>tag:www.qftest.com,2020-08-03:/en/blog/article/image-checks-in-a-world-of-font-antialiasing-and-cleartype.html</id><summary type="html">&lt;p&gt;My images are identical, why do my image checks fail?&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Image comparison across browsers&lt;/h2&gt;
&lt;p&gt;Everyone knows the phenomenon that websites are displayed differently depending on the browser. These differences are particularly noticeable between Internet Explorer and Microsoft Edge. You can also see this quite clearly on our homepage, as the following pictures illustrate:&lt;/p&gt;
&lt;p&gt;The following text was displayed with Microsoft Edge:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Text in Edge" src="/blog/resources/abbildvergleiche-3-text-in-edge.png" /&gt;&lt;/p&gt;
&lt;p&gt;This text was shown with Microsoft Internet Explorer:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Text in Internet Explorer" src="/blog/resources/abbildvergleiche-2-test-im-ie.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Very clearly different – right?&lt;/h2&gt;
&lt;p&gt;However, this has primarily nothing to do with Antialiasing or ClearType but is in the nature of the rendering engines of the respective browsers. Antialiasing and ClearType of course still play a role, but this would be masked due to the completely different display.&lt;/p&gt;
&lt;p&gt;It is immediately apparent that image comparisons are unsuitable here, as the display difference is significant and is visible to the naked eye.&lt;/p&gt;
&lt;p&gt;Image comparisons are obviously only suitable to a limited extent here, perhaps with an If/Else construct. However, a Check Text is clearly preferable.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Are image comparisons always useless then?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;It is left to the operating system (or additionally to the browser in the case of web tests) to render and display the components (such as buttons, checkboxes, etc.) and text. Image comparisons of &amp;#8220;whole&amp;#8221; components or components containing text are therefore always difficult and can lead to check errors when tests are executed in different environments.&lt;/p&gt;
&lt;p&gt;Generally, every operating system displays components differently. Linux displays buttons, check boxes, and of course also fonts differently than Windows. But even Windows 7 renders differently than Windows 10.&lt;/p&gt;
&lt;p&gt;Similarly, versions of Java as well as the graphics card driver used also play a role (especially in the case of font rendering): The browser and its respective version is also crucial for the display of web pages, as shown earlier.&lt;/p&gt;
&lt;p&gt;In summary, this means that such image comparisons may fail with any Windows update/upgrade, Java update, graphics card driver update, browser update, or changed user settings.&lt;/p&gt;
&lt;p&gt;And not only hardware and software are crucial, but also what respective users will set for themselves personally….&lt;/p&gt;
&lt;p&gt;But you have already seen all of this in the introduction above. Topic exhausted? &lt;strong&gt;No, we have not even started yet!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Wouldn’t it be &amp;#8220;bad&amp;#8221; enough that operating systems, graphic card drivers and Java versions play a role in the display? No, we are just getting to the tricky part: &lt;strong&gt;Antialiasing and ClearType!&lt;/strong&gt; &lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Excursion into theory: Font Antialiasing and ClearType&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The topics of Font Antialiasing and ClearType are much more subtle – different settings are often not visible to the naked eye, even though the images (especially around the edges) have virtually no identical color. Most users only notice in a direct comparison whether a text has been modified at all.&lt;/p&gt;
&lt;p&gt;These features don’t only affect web applications, but every application.&lt;/p&gt;
&lt;p&gt;If you are interested in learning more about this, I recommend the following Wikipedia entries as an introduction:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Anti-aliasing"&gt;https://en.wikipedia.org/wiki/Anti-aliasing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Subpixel_rendering"&gt;https://en.wikipedia.org/wiki/Subpixel_rendering&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Back to the practical application&lt;/h2&gt;
&lt;p&gt;Find all the differences in the following images:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Text in Edge" src="/blog/resources/abbildvergleiche-3-text-in-edge.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Text in Edge Ct2" src="/blog/resources/abbildvergleiche-4-text-in-edge_ct2.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;They are identical… or are they?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At a first glance, sure, maybe even at the second glance… but have you looked at the details more closely? Tip: if you touch the monitor with your nose (not on a touchscreen), you’ll notice subtle differences, like with the first &amp;#8220;A&amp;#8221; for example.&lt;/p&gt;
&lt;p&gt;Here it is enhanced:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A in Edge" src="/blog/resources/abbildvergleiche-5-edge_A.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="A in Edge Ct2" src="/blog/resources/abbildvergleiche-6-edge_A_ct2.png" /&gt;&lt;/p&gt;
&lt;p&gt;Now you should be able to see the individual pixels (every square corresponds to one logical monitor pixel) and how the edges of the letters are displayed differently. Do you remember the excursion into theory with the Wikipedia entries? If you have skipped over the theory, now is a good time to catch up. &lt;/p&gt;
&lt;p&gt;By the way: the only difference that leads to these two results is a minimally changed ClearType setting. Everything else is absolutely identical!&lt;/p&gt;
&lt;p&gt;Human eyes first identify the images as identical – QF-Test however immediately recognizes these images as different and thus first protocols an error at this failed image comparison.&lt;/p&gt;
&lt;p&gt;In the run log you can also look at an XOR of the two images and will very quickly see that the images actually differ significantly from each other (XOR highlights color differences depending on the colors; black means no difference).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image comparison XOR" src="/blog/resources/abbildvergleiche-7-xor.png" /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Antialiasing and ClearType in the user’s hand&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;With Font Antialiasing and Cleartype, the following settings are of significance, the first four of these depending on user sessions and their respective settings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;#8220;ClearType&amp;#8221;: is adjusted via Windows control panel (cross-application for all applications)&lt;/li&gt;
&lt;li&gt;graphics card driver settings: setting optings depending on the graphics card manufacturer and the driver used (cross-application for all applications)&lt;/li&gt;
&lt;li&gt;Java settings in the Java configuration (found via Windows control panel) (Java applications only)&lt;/li&gt;
&lt;li&gt;Browser settings (browser applications only)&lt;/li&gt;
&lt;li&gt;Java settings via system property or directly as part of the source code of the application (Java applications only)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;QF-Test solution&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Usually we never recommend verifying text in functional tests by image, we instead recommend an explicit check text node. Also, &amp;#8220;whole&amp;#8221; components should not be verified by image but by content checks, such as Check Text, Check boolean and so on, see also &lt;a href="https://www.qftest.com/doc/manual/en/checks.html#sec_checks"&gt;https://www.qftest.com/doc/manual/en/checks.html#sec_checks&lt;/a&gt; and especially: &lt;a href="https://www.qftest.com/doc/manual/en/checks.html#step_CheckBooleanStep"&gt;https://www.qftest.com/doc/manual/en/checks.html#step_CheckBooleanStep&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In case of tests that only differ in relation to Antialiasing and ClearType (i.e. not like the introductory example!) you can also choose to use an image comparison algorithm for the respective check image nodes, for example:&lt;/p&gt;
&lt;p&gt;identity;expected=0.91&lt;/p&gt;
&lt;p&gt;This means that more than 91% of all pixels need to be absolutely identical, but 9% are allowed to be different – you can intensify or soften this as you wish.&lt;/p&gt;
&lt;p&gt;Alternatively, even better for the specific example:&lt;/p&gt;
&lt;p&gt;similarity;expected=0.98&lt;/p&gt;
&lt;p&gt;Here, a similarity between expected and got color value is calculated for every pixel, and the average of all color values must be 98% identical, which the QF-Test procotol also tells us in the result of the checks:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;The images are within tolerance:  
 Expected probability: 98.0 %  
 Got probability: 98.56 %&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can find more about the algorithms here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.qftest.com/doc/manual/en/tech_imagealgorithmdetails.html#sec_imagealgorithmdetails"&gt;https://www.qftest.com/doc/manual/en/tech_imagealgorithmdetails.html#sec_imagealgorithmdetails&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Important to know:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If you have calculated a suitable algorithm, make sure you also check that this algorithm can differentiate clearly between &amp;#8220;good enough&amp;#8221; and &amp;#8220;not good enough&amp;#8221;, so that you don’t get any false positive results!&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Tip:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You need to input the algorithm manually in the field ‘algorithm for image comparison’. But you can also implement your own checkers and maybe even have different check images in the check input menu, which all differ in the algorithms used – but this is a topic for a different blog post.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Addressing multiple browser windows</title><link href="https://www.qftest.com/en/blog/article/addressing-multiple-browser-windows.html" rel="alternate"/><published>2020-06-09T00:00:00+02:00</published><updated>2020-06-09T00:00:00+02:00</updated><author><name>Plamen Vesselinov</name></author><id>tag:www.qftest.com,2020-06-09:/en/blog/article/addressing-multiple-browser-windows.html</id><summary type="html">&lt;p&gt;With multiple opened browser windows, how can I ensure that the correct is used?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Take the demo from here: &lt;a href="/blog/resources/Chrome_popup_window_name_blog.qft"&gt;Chrome_popup_window_name_blog.qft&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;With multiple opened browser windows, how can I ensure that the correct is used?&lt;/p&gt;
&lt;h2&gt;In order to distinguish between browser windows QF-Test requires additional information.&lt;/h2&gt;
&lt;p&gt;This information specifies which window should be used for replaying events. The attribute &amp;#8216;Name of the browser window&amp;#8217; has to be set in both &amp;#8216;Wait for document to load&amp;#8217; and the recorded &amp;#8216;Web page&amp;#8217;. In the &amp;#8216;Web page&amp;#8217; Component set it to &lt;code&gt;{default:windowname:}&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Important&lt;/h2&gt;
&lt;p&gt;Do not forget to set the variable &lt;code&gt;windowname&lt;/code&gt; before replying the required test actions.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.qftest.com/doc/manual/en/faq.html#faq_25" title="FAQ The web-application opens a popup-window"&gt;Manual FAQ&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>WebSaga | Wake up!</title><link href="https://www.qftest.com/en/blog/article/websaga-wake-up.html" rel="alternate"/><published>2020-05-28T00:00:00+02:00</published><updated>2020-05-28T00:00:00+02:00</updated><author><name>Beáta Pázmány</name></author><id>tag:www.qftest.com,2020-05-28:/en/blog/article/websaga-wake-up.html</id><summary type="html">&lt;p&gt;This is how it starts every night. I barely have a moment of rest until I&amp;#8217;m clicked and checked again. But it&amp;#8217;s fun, I&amp;#8217;m the website www.qftest.com!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Wake up! The daily tickle units are beginning! Let&amp;#8217;s see if you can still dance.&lt;/p&gt;
&lt;p&gt;This is how it starts every night. I barely have a moment of rest until I&amp;#8217;m clicked and checked again. But it&amp;#8217;s fun, I&amp;#8217;m the website &lt;a href="/en/index.html"&gt;www.qftest.com&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;First they used the quickstart assistent to prepare the basis for a dependency. It tests if I&amp;#8217;m already awake and ready to dance the next steps &amp;ndash; or do I still need to be woken up?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dependency in QF-Test for testing website" src="/blog/resources/websaga-wakeup-1-dependency.png" /&gt;&lt;/p&gt;
&lt;p&gt;Then I need to quickly jump through a hurdle &amp;ndash; the cookie banner. Every time I am woken up new it stands before me and asks in capital letters if I&amp;#8217;ll accept it. This only happens in the automation with QF-Test because the parameter &amp;#8220;deleteCookies&amp;#8221; is set on &amp;#8220;true&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Procedure in QF-Testfor testing website" src="/blog/resources/websaga-wakeup-2-procedure.png" /&gt;&lt;/p&gt;
&lt;p&gt;This hurdle is like a warm-up exercise. As soon as I&amp;#8217;m awake and the next dance begins, I don&amp;#8217;t need to do this exercise anymore.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Teststeps in QF-Test for testing website" src="/blog/resources/websaga-wakeup-3-test-steps.png" /&gt;&lt;/p&gt;
&lt;p&gt;If I make a big mistake and can&amp;#8217;t go on at all (we call it &amp;#8220;Exception&amp;#8221;) I&amp;#8217;m allowed to take a short rest and continue from the next dance. &amp;#8220;Go sleep!&amp;#8221;, they tell me, even though I&amp;#8217;ll be awoken again immediately. This is done by the dependency. Cleaning up and preparing until all the dances are completed. Only then can I truly rest.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cleanup in QF-Test for testing website" src="/blog/resources/websaga-wakeup-4-cleanup.png" /&gt;&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s a special card being used for the choreography &amp;ndash; this way all my trainers will know where to tickle me, so that I jump to the right place. The secret to this is the CustomWebResolver(). I will talk about that more next time.&lt;/p&gt;
&lt;p&gt;Until next time &amp;ndash; happy clicking and checking!&lt;/p&gt;</content><category term="blog"/><category term="how-to"/><category term="websaga"/></entry><entry><title>Analyzing ComponentNot­FoundExceptions</title><link href="https://www.qftest.com/en/blog/article/analyzing-componentnotfoundexceptions.html" rel="alternate"/><published>2020-05-26T00:00:00+02:00</published><updated>2020-05-26T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2020-05-26:/en/blog/article/analyzing-componentnotfoundexceptions.html</id><summary type="html">&lt;p&gt;In case QF-Test is unable to identify the corresponding component in the application a ComponentNotFoundException gets thrown. Whenever an exception occurs QF-Test (by default) logs a screenshot.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="QF-Test ComponentNotFoundException Mouseclick" src="/blog/resources/ComponentNotFound-3-mouseclick-en.png" title="ComponentNotFoundException Mouseclick" /&gt;&lt;/p&gt;
&lt;p&gt;In the above image, you can see a mouse click targeting a component (named mFile) in the application. Now whenever an action (like a mouse click) or a verification gets replayed QF-Test first needs to find the corresponding component in the application.&lt;/p&gt;
&lt;p&gt;In case QF-Test is unable to identify the corresponding component in the application a ComponentNotFoundException gets thrown.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test ComponentNotFoundException" src="/blog/resources/componentNotFound-1-en.png" title="ComponentNotFoundException" /&gt;&lt;/p&gt;
&lt;p&gt;The following blog article delineates the different actions needed in order to analyse the root cause of such a ComponentNotFoundException.&lt;/p&gt;
&lt;h2&gt;Check that the component was present in the moment the action / verification got replayed&lt;/h2&gt;
&lt;p&gt;Although the above step may sound naive, users often miss to identify whether the target component is actually present in the application. In the moment QF-Test is replaying the action / the verification of course, not later on!&lt;/p&gt;
&lt;p&gt;Whenever an exception occurs QF-Test (by default) logs a screenshot. Press the &amp;#8220;Jump to run log&amp;#8221; button (see above error dialog) or press &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;L&lt;/kbd&gt; (in case you already closed that dialog) in order to open the run log. In the run log press the &amp;#8220;Find next error&amp;#8221; icon (see screenshot below) in order to jump to the exception. Now have a look at the screenshots in that run log. Is the target component visible there? If yes, then continue by reading the next chapter &amp;#8220;Check why QF-Test was unable to find the component although it is present in my application&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test ComponentNotFoundException Runlog" src="/blog/resources/componentNotFound-2-Log-en.png" title="ComponentNotFoundException Runlog" /&gt;&lt;/p&gt;
&lt;p&gt;If the component is not visible, please check&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the application needs more time till it displays the component&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here, it is likely that QF-Test was too impatient. QF-Test tried to find the target component although the application is not yet showing the component. Instead the application is for example showing a &amp;#8220;I&amp;#8217;m busy&amp;#8221;-animation (like a progressbar).&lt;/p&gt;
&lt;p&gt;Often users tend to work around this issue by specifing a waiting time in the &amp;#8216;Delay before&amp;#8217; or the &amp;#8216;Delay after&amp;#8217; attributes. However this is &amp;ndash; most often &amp;ndash; not recommended. In order to increase test execution speed, it is preferable to use a &amp;#8216;Wait for component&amp;#8217;, a &amp;#8216;Wait for document to load&amp;#8217; or a &amp;#8216;Wait for component absence&amp;#8217; node (with the latter it is possible to wait till the processbar disappears).&lt;/p&gt;
&lt;p&gt;In order to insert a &amp;#8216;Wait for component&amp;#8217;: Copy/Paste the action / verification step that throwed the ComponentNotFoundException while replaying. (You now have this step two times). Then right click the FIRST step → Transform node into → Wait for component.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the action/verification is targeting a component that only appears sometimes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here, the application is sometimes coming up with something that needs to get handled. This could for example be a dialog with a message like &amp;#8220;Could not connect to server&amp;#8221; that sporadically occurs after a given action.&lt;/p&gt;
&lt;p&gt;Well, if you have an action targeting a component that sometimes appears (like a click on the OK-Button of such a dialog) and that sometimes does not appear (and that doesn&amp;#8217;t matter for the rest of the test) &amp;ndash; you can use a try/catch construct (This construct tells QF-Test to ignore any exception for that particular step). The fastest way to get such a construct, is to right click the failing action node → Pack nodes → Try.&lt;/p&gt;
&lt;p&gt;Alternatively, an &amp;#8216;If&amp;#8217; node can be used in order to verify whether the target component is present before the action/verification node is getting executed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the action/verification is targeting a component that only appears when certain conditions are met&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example the target component of your click/verification step is only available after the login step.&lt;/p&gt;
&lt;p&gt;Although try/catch constructs as well as &amp;#8216;If&amp;#8217; nodes (see above point &amp;#8220;if the action/verification is targeting a component that only appears sometimes&amp;#8221;) often help here, too, it should be mentioned, that &amp;#8216;Dependency&amp;#8217; nodes may be preferable here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the component needs to get scrolled visible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, here scolling logic needs to get added. (Actually scrolling in QF-Test is an interesting topic for another blog article.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if this is a bug in your application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That means that the component is not available although it should be available. For example a component indicating an error message that should be there &amp;ndash; but somehow that error message is not present.&lt;/p&gt;
&lt;p&gt;Well this is something you need to discuss with your dev or bugfixing team ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the target component was removed by development team&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is also something that you need to discuss internally. Maybe the test case should get removed or rewritten?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the target component is not shown because a previous action failed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is rarely the case. But sometimes a previous action fails and QF-Test does not recognize that the previous action failed (e.g. a click that then opens a dialog with the desired component). Here the task is to analyze why the previous action failed.&lt;/p&gt;
&lt;h2&gt;Check why QF-Test was unable to find the component although it is present in my application&lt;/h2&gt;
&lt;p&gt;In order to avoid any misunderstandings, let me briefly discuss some fundamental ideas of the component recognition algorithm that is used by QF-Test.&lt;/p&gt;
&lt;p&gt;The &amp;#8220;QF-Test component ID&amp;#8221; attribute of a an action/verification is not describing the target component by itself. Instead the string is referring to a node in the &amp;#8220;Windows and Components&amp;#8221; subtree of QF-Test. E.g. if we take the above mouse click:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test ComponentNotFoundException Mouseclick" src="/blog/resources/ComponentNotFound-3-mouseclick-en.png" title="ComponentNotFoundException Mouseclick" /&gt;&lt;/p&gt;
&lt;p&gt;Then this mouse click is referring to the mFile-node (right-click the node → Locate component):&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test ComponentNotFoundException Runlog Message" src="/blog/resources/componentNotFound-4-LogMsg_en.png" title="ComponentNotFoundException Runlog Message" /&gt;&lt;/p&gt;
&lt;p&gt;It is the information in that referred &amp;#8220;component definition node&amp;#8221; that is used by the component recognition algorithm. In fact the string &amp;#8220;mFile&amp;#8221; is just a reference (Feel free to change the QF-Test ID of any component to make your tests more readable).&lt;/p&gt;
&lt;p&gt;The reason why the recognition information is kept in one place is the following: It can be that this component in your application changes and then QF-Test may not be able to re-recognize that component anymore (e.g. the text of a button changes from OK to Continue). However you can have an arbitrary number of actions/verification nodes in your test suite that target this component (e.g. 500 clicks on that button). Now instead of going through your test suite and check where a mouse click is targeting that component, you only have to fix one node &amp;#8230;&lt;/p&gt;
&lt;p&gt;As you may already know, QF-Test is using a probability algorithm for component recognition. And that algorithm determines each component in the component tree one after another. So in the image above, QF-Test will first search for the window, then for the MenuBar and then for the MenuItem named mFile.&lt;/p&gt;
&lt;p&gt;Because QF-Test first searches for the window component etc. before it is trying to locate the target component, we need to find out which component QF-Test is not able to find. Is it the window itself (e.g. because the title of the window changed from &amp;#8220;MyApplication &amp;ndash; version 1.0&amp;#8221; to &amp;#8220;MyApplication &amp;ndash; version 2.0&amp;#8221;)? Or is it any intermediate component?&lt;/p&gt;
&lt;p&gt;If you have the possibility, simply re-record a click or check on the target component (And don&amp;#8217;t forget to delete the recording after analysis &amp;ndash; otherwise your test suite gets littered with recording / component definition nodes). Then by comparing the recorded versus the existing &amp;#8220;Window and components&amp;#8221; subtree, you can see where the tree differs &amp;ndash; and thus which component definition node QF-Test was unable to find in your application.&lt;/p&gt;
&lt;p&gt;For example let&amp;#8217;s re-record the click on the File-menu:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test ComponentNotFoundException Component Tree Diff" src="/blog/resources/componentNotFound-5-ComponentTreeDiff-en.png" title="ComponentNotFoundException Component Tree Diff" /&gt;&lt;/p&gt;
&lt;p&gt;Here we can see that the window component is still the same (marked black) so QF-Test is still able to find / re-recongize the window component. Next is the MenuBar (marked red). And here we have two different component nodes &amp;#8230; This means, that QF-Test is unable to re-recognize the MenuBar component.&lt;/p&gt;
&lt;p&gt;Another way is to have a look at the run log. Whenever QF-Test is not able to find a component it is logging a message indicating the components present in the application and why QF-Test excluded which component:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test ComponentNotFoundException Runlog Message" src="/blog/resources/componentNotFound-4-LogMsg_en.png" title="ComponentNotFoundException Runlog Message" /&gt;&lt;/p&gt;
&lt;p&gt;After we have identified the component definition node that causes the problem check which node attribute changed. Then have a look at the following table:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the class attribute changed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The class attribute is indicating the component type QF-Test should look for. E.g. whether QF-Test should search for a Button, a List, a TextArea &amp;#8230;&lt;/p&gt;
&lt;p&gt;It rarely happens, that the component class changes (e.g. that a button get&amp;#8217;s replaced by a label with the same functionality). If you nevertheless have that case, just change the class attribute accordingly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the name attribute changed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The name attribute is the most dedicated attribute for component recognition. It should be a unique identifier of the given component in your application.&lt;/p&gt;
&lt;p&gt;Developers can add such an identifier to each component in your application. However because these identifier are not visible to the user of your application, this is not often done. Furthermore these identifier are sometimes misused for other purposes &amp;ndash; e.g. to store additional component related data.&lt;/p&gt;
&lt;p&gt;If only one component is affected by a name attribute change, you can try whether clearing this attribute helps. Otherwise a NameResolvers may help in order to change whether and what QF-Test records as component name.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the feature attribute changed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The feature attribute gives the main feature QF-Test should use in order to recognize the component. This may for example be the the text of a component (e.g. Ok in case of an Ok-Button).&lt;/p&gt;
&lt;p&gt;If you have any variable parts in the feature (e.g. the title of your window may either be &amp;#8220;MyApplication version 1.0&amp;#8221; or &amp;#8220;MyApplication version 2.0&amp;#8221; and so on), you can click the &amp;#8220;As regex&amp;#8221; checkbox and specify a regular expression, that then should match the feature.&lt;/p&gt;
&lt;p&gt;Via an FeatureResolver it is possible to change what QF-Test takes/records as feature.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if any extra feature attribute changed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional features which QF-Test may use in order to recognizes your component (similar to the feature attribute).&lt;/p&gt;
&lt;p&gt;You can use an ExtraFeatureResolver to change (add/remove) extra features. Similar to a NameResolver / FeatureResolver an ExtraFeatureResolver can also affect which extra features QF-Test records (records at all / records by default).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if any structure or geometry attribute changed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The structure / geometry is not often taken into account by QF-Test. In fact, this can only be an issue if QF-Test does not have a good name / feature or extra feature. Then QF-Test may recognize your component based on structure and/or geometry &amp;ndash; e.g. if you have multiple OK-Buttons in your application and all buttons have the same name / feature / extra feature (or even no name / feature / extra feature).&lt;/p&gt;
&lt;p&gt;So as long as you do have good name / feature or extra feature attributes &amp;#8230; ignore it (or correct / remove these geometry / structure attributes).&lt;/p&gt;
&lt;p&gt;However when you do not have any good name / feature or extra feature attributes. Take the time to write some of the above mentioned resolver scripts for your component. This is often better then to rely on structure and geometry. (There are some problems with geometry based component recognition. E.g. replaying your tests on different machines often fails, as smaller / larger screens tend to change the geometry where a component gets rendered &amp;#8230;)&lt;/p&gt;
&lt;h2&gt;Video on this subject&lt;/h2&gt;
&lt;div class="video-card-horizontal flex-row-dynamic flex-wrap"&gt;
    &lt;a  href="https://www.qftest.com/en/support/videos/component-recognition-special-webinar.html" rel="bookmark" title="Find the keys &amp;ndash; UI test automation needs robust component recognition"&gt;
        &lt;img src="/videos/resources/component-recognition-special-webinar-en.jpg" class="scroll-animated" width="640" height="480" alt=""&gt;
    &lt;/a&gt;
    &lt;div class="entry-content"&gt;
      &lt;h3 class="entry-title"&gt;
        &lt;a class="title" href="https://www.qftest.com/en/support/videos/component-recognition-special-webinar.html" rel="bookmark" title="Find the keys &amp;ndash; UI test automation needs robust component recognition"&gt;
            Find the keys &amp;ndash; UI test automation needs robust component recognition
        &lt;/a&gt;
      &lt;/h3&gt;
    &lt;p&gt;Component recognition in QF-Test is quite flexible. In addition to standard controls like buttons or checkboxes it also supports complex components (dynamic trees, tables etc.). This webinar shows the basics of this algorithm and configuration options for Java, Web and Windows applications. We will explain how to achieve robust component …&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Remarks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Be sure to run your tests in an unlocked, active desktop. Otherwise you may get sporadic ComponentNotFoundExceptions everywhere (and if you rerun your test, other action/verification steps will/may be affected).&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>WebSaga | Click click – it tickles</title><link href="https://www.qftest.com/en/blog/article/websaga-click-click-it-tickles.html" rel="alternate"/><published>2020-05-14T00:00:00+02:00</published><updated>2020-05-14T00:00:00+02:00</updated><author><name>Beáta Pázmány</name></author><id>tag:www.qftest.com,2020-05-14:/en/blog/article/websaga-click-click-it-tickles.html</id><summary type="html">&lt;p&gt;Now it&amp;#8217;s time! Now I can introduce myself to the world. Here I am! Can you see me? Hi! I&amp;#8217;m the new website, www.qftest.com!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Click click &amp;ndash; it tickles! I&amp;#8217;m jumping back and forth, it&amp;#8217;s almost like a dance. Always the same steps, I show a different form with every jump.&lt;br /&gt;
 My educators (I call them colleagues) have been training me for a long time, until I learned to do it perfectly. Quality first, as they always say. So that I always jump to the right spot, always show my best form.&lt;br /&gt;
 My predecessor was pretty good too, but they&amp;#8217;re old now and deserved their retirement. I learned a lot from them, too. &lt;/p&gt;
&lt;p&gt;Now it&amp;#8217;s time! Now I can introduce myself to the world. Here I am! Can you see me? Hi! I&amp;#8217;m the new website, &lt;a href="/en/index.html"&gt;www.qftest.com&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;My colleagues say, it&amp;#8217;s not enough to learn the steps, I can&amp;#8217;t forget them either, or make wrong steps. They call it &amp;#8220;bugs&amp;#8221;. (Why not sidesteps?)  &lt;/p&gt;
&lt;p&gt;Everyday they want to click, click, tickle me! And check, check, verify me!&lt;br /&gt;
 They talk about test automation, regression tests, and that I will also need a resolver! Because of my components. I also heard them talk about ProcBuilder!&lt;br /&gt;
 Are you excited, too? Soon I will report here how my daily tickle and check units are built, developed and protocoled, or as the colleagues say: how I am tested automatically.  &lt;/p&gt;
&lt;p&gt;See you soon and happy clicking and checking!&lt;/p&gt;</content><category term="blog"/><category term="how-to"/><category term="websaga"/></entry><entry><title>Fantastic Electron apps – and how to test them</title><link href="https://www.qftest.com/en/blog/article/fantastic-electron-apps-and-how-to-test-them.html" rel="alternate"/><published>2020-05-07T00:00:00+02:00</published><updated>2020-05-07T00:00:00+02:00</updated><author><name>Daniel Rieth</name></author><id>tag:www.qftest.com,2020-05-07:/en/blog/article/fantastic-electron-apps-and-how-to-test-them.html</id><summary type="html">&lt;p&gt;A very good solution of the combination of web technology and desktop applications is Electron. Almost every user knows or uses Electron apps. Popular apps like Skype, Microsoft Visual Studio Code or Discord are built using the Electron framework.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Last update: 2022-01-14&lt;/h2&gt;
&lt;p&gt;Even after Electron&amp;#8217;s own test framework &amp;#8220;Spectron&amp;#8221; is no longer being developed, it is still possible to professionally test your Electron apps with QF-Test &amp;ndash; since &lt;a href="/en/product/release-notes-roadmap.html#a25721-61bb38298803c350361685"&gt;version 5.4.0&lt;/a&gt; even better than ever before thanks to CDP connection mode!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Molecule Electron &amp;ndash; Testing Electron applications" src="/images/icons/streamline-icon-science-molecules@150x150-blau-212x212.svg" title="Testing electron applications with QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;Many developers &amp;ndash; 72% according to a &lt;a href="https://stackoverflow.co/internal/"&gt;survey from stackoverflow&lt;/a&gt; (german version) &amp;ndash; are specialized in web development. Web technologies are widely spread and supported by basically all operating systems. They provide a huge range of frameworks to the developers. This doesn’t make desktop applications obsolete. Why not use existing resources from the web for local applications? As the headline states there is already a solution for this: Electron.&lt;/p&gt;
&lt;h2&gt;Testing Electron apps with QF-Test&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=R630MCJd0Z4&amp;amp;list=PLsQ3ggMe67qgEd4HTruyIgzvdq0RYOzBX"&gt;Video version&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Electron is a framework for executing cross-platform desktop applications using the web browser Chromium and the Node.js framework.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.qftest.com/doc/manual/en/web_electron.html#usec_electron" title="Testing Electron applications"&gt;Further information&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Almost every user knows or uses Electron apps. Popular apps like &lt;em&gt;Skype&lt;/em&gt;, &lt;em&gt;Microsoft Visual Studio Code&lt;/em&gt; or &lt;em&gt;Discord&lt;/em&gt; are built using the Electron framework. Electron origins from GitHubs well known editor Atom which gave it its initial name &amp;#8220;&lt;em&gt;Atom Shell&amp;#8221;.&lt;/em&gt; The idea of GitHub was to combine the browserless &amp;#8220;&lt;em&gt;Node.js&lt;/em&gt;&amp;#8221; framework with the Chromium rendering engine. This provides an easy and fast way to create desktop apps out of existing web apps. Except for the initial download there is no need for an network connection.&lt;/p&gt;
&lt;p&gt;Apps can now be developed using web technologies like HTML, CSS and JavaScript. This enables apps to be developed OS independent which makes them easier to maintain. Even on the code base it is quite easy to handle native functions like menus, notifications, access to the file system and also some low-level informations about the hardware.&lt;/p&gt;
&lt;p&gt;The best summary for the framework comes from the official site:  &lt;a href="https://www.electronjs.org/"&gt;&amp;#8220;It takes care of the hard parts so you can focus on the core of your application.&amp;#8221;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But I don’t write this to sing the praises of Electron &amp;ndash; of course I want to mention that QF-Test supports testing of Electron apps since version 4.5 and I want to give a quick introduction.&lt;/p&gt;
&lt;h2&gt;How to test Electron Applications with QF-Test&lt;/h2&gt;
&lt;p&gt;The basic concept is still the same. The best way to connect to an Electron app is by using our Quickstart Wizard (Menu: Extras → Quickstart Wizard). Then select &amp;#8220;An Electron application&amp;#8221; and on the next page you select the app you want to test plus option command line parameters.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Quickstart Wizard for Electron apps" src="/blog/resources/electron-en.png" title="Quickstart Wizard for Electron apps" /&gt;&lt;/p&gt;
&lt;p&gt;The next step is to specify the ChromeDriver. You can either choose the correct Electron version or let QF-Test detect it so you know what ChromeDriver version you need. QF-Test can then automatically download the ChromeDriver.&lt;/p&gt;
&lt;p&gt;Electron provides all features already known from the web. Additionally native menus can be controlled by a Selection step (Insert-&amp;gt;Event  nodes-&amp;gt;Selection). The syntax looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="mainWindow = new BrowserWindow..." src="/blog/resources/electron-en-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;To be able to control this functionality QF-Test has to get access to the Electron API. This means that the following values (currently default) inside the BrowserWindow should not be changed.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;mainWindow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BrowserWindow&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;webPreferences:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;enableRemoteModule:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;contextIsolation:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;nodeIntegration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Please try it yourself and test your own app! If you only have an older version of QF-Test (&amp;lt;4.5) you can get the latest version for free from this site and evaluate it: &lt;a href="https://services.qftest.com/en/license/request/"&gt;Free trial&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>The proof of the pudding is in the eating – Canned Procedures</title><link href="https://www.qftest.com/en/blog/article/the-proof-of-the-pudding-is-in-the-eating-canned-procedures.html" rel="alternate"/><published>2020-04-08T00:00:00+02:00</published><updated>2020-04-08T00:00:00+02:00</updated><author><name>Beáta Pázmány</name></author><id>tag:www.qftest.com,2020-04-08:/en/blog/article/the-proof-of-the-pudding-is-in-the-eating-canned-procedures.html</id><summary type="html">&lt;p&gt;Packet soup or baking mixture, cook by yourself or bake? Concerning food opinions divide. Pros and cons. QF-Test doesn’t ship convenience food, you have to cook it yourself. But you will be given a variety of spices that will ease your life: the standard library qfs.qft. From a collection of fine salt and pepper to complex pizza spices, the genuine for every meal.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Packet soup or baking mixture, cook by yourself or bake? Concerning food opinions divide. Pros and cons.&lt;/p&gt;
&lt;p&gt;QF-Test doesn’t ship convenience food, you have to cook it yourself. But you will be given a variety of spices that will ease your life: the standard library qfs.qft. From a collection of fine salt and pepper to complex pizza spices, the genuine for every meal.&lt;/p&gt;
&lt;p&gt;What’s inside the standard library?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;procedures (spices)&lt;/li&gt;
&lt;li&gt;generic components (vitamins)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fish, meat or vegetarian? Some spices go together with any meal, other just with specific dishes. The standard library contains drawers – the packages – where you can find the procedures waiting for their service.&lt;/p&gt;
&lt;p&gt;For example: What should I take to cook pasta? Let’s take a look inside the &amp;#8220;pasta&amp;#8221; drawer. With mushrooms? With cheese? Pesto?&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test standard library kitchen" src="/blog/resources/standardlib-en-kitchen.png" title="QF-Test standard library kitchen" /&gt;&lt;/p&gt;
&lt;p&gt;In qfs.qft you have those packages for engines such as &amp;#8220;swing&amp;#8221;, &amp;#8220;web&amp;#8221; or &amp;#8220;win&amp;#8221; as well as universal procedures in &amp;#8220;utils&amp;#8221; drawer.&lt;/p&gt;
&lt;p&gt;Do I only take salt and pepper (mouse click)? You have the refined version called &lt;code&gt;general.doClick()&lt;/code&gt; for every engine. Or italian spices for my salad? Then I take &lt;code&gt;swing.combobox.getItemCount()&lt;/code&gt;. Or something similar for pizza? Then &lt;code&gt;fx.list.getItemCount()&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test procedures in test suite" src="/blog/resources/standardlib-en-2.png" title="QF-Test procedures in test suite example" /&gt;&lt;/p&gt;
&lt;p&gt;What are the advantages? You are finished. Mastered by famed chefs, tested by gourmets. It saves my time and guarantees constant quality.&lt;/p&gt;
&lt;p&gt;And if it’s not my flavor? Then I can still write my own procedures.&lt;/p&gt;
&lt;p&gt;So why not give it a shot? Unlike eating a pudding, procedures won’t disappear if you try them… :)&lt;/p&gt;
&lt;p&gt;Enjoy your meal!&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>QF-Test 5.0 and macOS Catalina (10.15)</title><link href="https://www.qftest.com/en/blog/article/qf-test-50-and-macos-catalina-1015.html" rel="alternate"/><published>2020-02-10T00:00:00+01:00</published><updated>2020-02-10T00:00:00+01:00</updated><author><name>Pascal Bihler</name></author><id>tag:www.qftest.com,2020-02-10:/en/blog/article/qf-test-50-and-macos-catalina-1015.html</id><summary type="html">&lt;p&gt;In their recent version of macOS, named &amp;#8220;Catalina&amp;#8221; (10.15), Apple introduced a feature protecting users from executing malicious software downloaded from the Internet. To separate &amp;#8220;good&amp;#8221; from &amp;#8220;bad&amp;#8221; software, Apple implemented a so called &amp;#8220;notarization&amp;#8221; process, and software which has not been &amp;#8220;notarized&amp;#8221; cannot be executed on macOS Catalina without further ado.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In their recent version of macOS, named &amp;#8220;Catalina&amp;#8221; (10.15), Apple introduced a feature protecting users from executing malicious software downloaded from the Internet. To separate &amp;#8220;good&amp;#8221; from &amp;#8220;bad&amp;#8221; software, Apple implemented a so called &amp;#8220;notarization&amp;#8221; process, and software which has not been &amp;#8220;notarized&amp;#8221; cannot be executed on macOS Catalina without further ado.&lt;/p&gt;
&lt;p&gt;Unfortunately, Apple tightened the criterias for notarization by February 2020 and excludes within others all software from notarization which build upon Java 8 &amp;ndash; this also affects QF-Test in the latest version 5.0. Therefor, after downloading and moving QF-Test to the application folder, another step is required to execute QF-Test on those systems:&lt;/p&gt;
&lt;p&gt;After starting QF-Test a message is shown that Apple cannot check QF-Test for malicious software:&lt;/p&gt;
&lt;p&gt;&lt;img alt="macOS Catalina 10.15 Error Dialog" src="/blog/resources/qftest-50-macos-catalina-en-01-Error-Dialog.png" title="macOS Catalina 10.15 Error Dialog" /&gt;&lt;/p&gt;
&lt;p&gt;Close the dialog by clicking &amp;#8220;OK&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Then open the system preferences and there the section &amp;#8220;Security &amp;amp; Privacy&amp;#8221;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="macOS Catalina 10.15 Open System preferences" src="/blog/resources/qftest-50-macos-catalina-en-02-Open-System-Preferences.png" title="macOS Catalina 10.15 Open System preferences" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="macOS Catalina 10.15 Select security privacy" src="/blog/resources/qftest-50-macos-catalina-en-03-Select-Security-Privacy.png" title="macOS Catalina 10.15 Select security privacy" /&gt;&lt;/p&gt;
&lt;p&gt;In the tab &amp;#8220;General&amp;#8221; you can now find the remark that the start of QF-Test has been blocked.&lt;/p&gt;
&lt;p&gt;&lt;img alt="macOS Catalina 10.15 Click open anyway" src="/blog/resources/qftest-50-macos-catalina-en-04-click-Open-Anyway.png" title="macOS Catalina 10.15 Click open anyway" /&gt;&lt;/p&gt;
&lt;p&gt;Confirm the execution of QF-Test by clicking &amp;#8220;Open Anyway&amp;#8221;. In the following dialog, you can finally allow the execution of QF-Test:&lt;/p&gt;
&lt;p&gt;&lt;img alt="macOS Catalina 10.15 Allow Open Dialog" src="/blog/resources/qftest-50-macos-catalina-en-05-Allow-Open-Dialog.png" title="macOS Catalina 10.15 Allow Open Dialog" /&gt;&lt;/p&gt;
&lt;p&gt;Now you can use QF-Test like usual, the operating system remembers the exception.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>How to debug Jython Scripts in QF-Test</title><link href="https://www.qftest.com/en/blog/article/how-to-debug-jython-scripts-in-qf-test.html" rel="alternate"/><published>2020-01-30T00:00:00+01:00</published><updated>2020-01-30T00:00:00+01:00</updated><author><name>Karlheinz Kellerer</name></author><id>tag:www.qftest.com,2020-01-30:/en/blog/article/how-to-debug-jython-scripts-in-qf-test.html</id><summary type="html">&lt;p&gt;By the usage of script nodes QF-Test allows embedding of special custom functionality into your test cases. Typically, these scripts are supposed to be short and relatively simple containing just dedicated functionality, whereas the majority of test logic is done via QF-Test visual nodes.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;By the usage of script nodes QF-Test allows embedding of special custom functionality into your test cases. Typically, these scripts are supposed to be short and relatively simple containing just dedicated functionality, whereas the majority of test logic is done via QF-Test visual nodes.&lt;/p&gt;
&lt;p&gt;Verifying the correctness of the functionality of those scripts can be done mostly by inspection or inserting simple print statements for generating debug output into the QF-Test terminal (&lt;a href="/en/blog/article/news-about-the-print-and-println-module.html"&gt;see also blog about print&lt;/a&gt;).  Jython also comes with an integrated post mortem debugger to analyze the cause of exceptions within a script execution (&lt;a href="https://www.qftest.com/doc/manual/en/user_scripting.html#usec_jythondebug"&gt;see post mortem manual chapter&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Nevertheless there may be situations of more complex scripts that demand for enhanced ways of debugging, e.g.  by setting break points, executing step by step and analyzing variable values. To allow such debugging features you can make use of the PyDev, an IDE for Python development, available standalone or as an Eclipse Plugin.&lt;/p&gt;
&lt;p&gt;In this article I explain how to prepare an Eclipse or standalone Liclipse IDE to connect to the Jython interpreter of QF-Test and allow enhanced debugging.&lt;/p&gt;
&lt;h2&gt;IDE configuration&lt;/h2&gt;
&lt;p&gt;If you already use Eclipse IDE the following step 1 shows how to install PyDev as a plugin.&lt;/p&gt;
&lt;p&gt;Another option is to directly download the PyDev IDE called Liclipse that already contains the PyDev Plugin (&lt;a href="https://www.liclipse.com/"&gt;https://www.liclipse.com/&lt;/a&gt;). You can skip the first step then.  &lt;/p&gt;
&lt;h3&gt;Step 1: Eclipse Pydev Plugin installation (not required for Liclipse)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Open menu Help → Install New Software&lt;/li&gt;
&lt;li&gt;Provide Pydev URL &lt;a href="https://www.pydev.org/updates/"&gt;https://www.pydev.org/updates/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Select PyDev Package (for further Information see also &lt;a href="https://www.pydev.org/download.html"&gt;https://www.pydev.org/download.html&lt;/a&gt;)
&lt;img alt="PyDev Package" src="/blog/resources/debug-jython-scripts-en-1.png" title="PyDev Package" /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 2: Configure Jython Interpreter in Eclipse / Liclipse&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Open Menu Window → Preferences&lt;/li&gt;
&lt;li&gt;Choose  PyDev → Interpreters → Jython Interpreter&lt;/li&gt;
&lt;li&gt;Browse for Jython jar&lt;/li&gt;
&lt;li&gt;Navigate to lib\jython.jar inside your QF-Test version specific installation directory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Jython Interpreter" src="/blog/resources/debug-jython-scripts-en-2.png" title="Jython Interpreter" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An error message occurs, but please proceed anyways&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Jython interpreter error message" src="/blog/resources/debug-jython-scripts-en-3.png" title="Jython interpreter error message" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add the QF-Test jython\Lib directory&lt;/li&gt;
&lt;li&gt;New folder&lt;/li&gt;
&lt;li&gt;Select the jython\Lib directory from inside your QF-Test version specific directory to the Python path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="jython\Lib directory " src="/blog/resources/debug-jython-scripts-en-4.png" title="jython\Lib directory" /&gt;&lt;/p&gt;
&lt;p&gt;By this the basic Eclipse / Liclipse configuration is completed.&lt;/p&gt;
&lt;h2&gt;QF-Test configuration&lt;/h2&gt;
&lt;h3&gt;Step1: Add PyDev to the QF-Test python path&lt;/h3&gt;
&lt;p&gt;In order to add the necessary pydev modules to the python path, you can start QF-Test with the additional command line parameter&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-J-Dpython.path=&amp;lt;path to relevant pydev modules&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The relevant pydev modules are located in the pysrc directory inside the pydev.core plugin. The pydev.core plugin resides in the eclipse/liclipse plugins folder, starting with &lt;code&gt;org.python.pydev.core&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;e.g. for Liclipse is may look like:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;C:\Program Files\Brainwy\LiClipse 5.0.3\plugins\org.python.pydev.core_6.5.0.201809011413\pysrc&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;one possible location in Eclipse is:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;C:\Program Files\Eclipse\eclipse-4.11-64\plugins\org.python.pydev.core_7.2..201904261721\pysrc&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The QF-Test command line or shortcut for Liclipse, should look similar to this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;C:\Program Files\QFS\QF-Test\qftest-4.6.0-dev\bin\qftest.exe&amp;quot; -reuse -J-Dpython.path=&amp;quot;C:\Program Files\Brainwy\LiClipse 5.0.3\plugins\org.python.pydev.core_6.5.0.201809011413\pysrc&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And for Eclipse respectively:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;C:\Program Files\QFS\QF-Test\qftest-4.6.1-dev\bin\qftest.exe&amp;quot; -reuse -J-Dpython.path= &amp;quot;T:\common\eclipse\eclipse-4.11-64\plugins\org.python.pydev.core_7.2.1.201904261721\pysrc&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Step 2: Configure QF-Test to always save Jython temp scripts&lt;/h3&gt;
&lt;p&gt;QF-Test writes the Jython script nodes into temporary files (called e.g. &lt;code&gt;_script3452345823457.py&lt;/code&gt;, located in the QF-Test user directory), but typically only once or when an error occurs. Because those temporary files are now used by the PyDev Debugger, it is very recommended to configure QF-Test to update these files whenever the QF-Test Jython script node is changed. This can be achieved by setting a respective QF-Test option by use of a server script node with the content&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rc.setOption(Options.OPT_PLAY_SCRIPT_JYTHON_SAVE, Options.VAL_PLAY_SCRIPT_JYTHON_SAVE_IF_CHANGED)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which needs to be executed once before starting with debugging.&lt;/p&gt;
&lt;h3&gt;Step 3: Set Eclipse/Liclipse as Command for external editor (optional)&lt;/h3&gt;
&lt;p&gt;When having Eclipse/Liclipse around, it may prove useful to be configured as default external editor for QF-Test Jython scripts. This can be achieved by following steps in QF-Test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the QF-Test Options via Edit → Options&lt;/li&gt;
&lt;li&gt;Select option: Generel → External Applications&lt;/li&gt;
&lt;li&gt;Set the external editor command to point to the Eclipse/Liclipse executable with the parameter &lt;strong&gt;–launcher.openFile&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Press OK&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="External programs" src="/blog/resources/debug-jython-scripts-en-5.png" title="External programs" /&gt;&lt;/p&gt;
&lt;p&gt;Now script nodes are directly opened in Eclipse/Liclipse when the Button &amp;#8220;edit in external editor&amp;#8221; is pressed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Edit in external editor" src="/blog/resources/debug-jython-scripts-en-6.png" title="Edit in external editor" /&gt;&lt;/p&gt;
&lt;h3&gt;Step 4: Prepare script for remote debugging&lt;/h3&gt;
&lt;p&gt;In order to prepare your Jython script to be debugged, you need to add some line of codes &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pydevd&lt;/span&gt;
&lt;span class="n"&gt;pydevd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stoptrace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pydevd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5678&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;suspend&lt;/span&gt;&lt;span class="o"&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="n"&gt;stdoutToServer&lt;/span&gt;&lt;span class="o"&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="n"&gt;stderrToServer&lt;/span&gt;&lt;span class="o"&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="n"&gt;overwrite_prev_trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;→ here execution will be interrupted and control switches to pydev debugger&lt;/p&gt;
&lt;p&gt;&lt;img alt="Prepare Jython script to be debugged" src="/blog/resources/debug-jython-scripts-en-7.png" title="Prepare Jython script to be debugged" /&gt;&lt;/p&gt;
&lt;h2&gt;Debug Jython scripts&lt;/h2&gt;
&lt;h3&gt;Step 1: Start PyDev Debug Server&lt;/h3&gt;
&lt;p&gt;In order to allow PyDev to interrupt and control the Jython script execution, the pydev debug server needs to be started in Eclipse/Liclipse&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Move to Debug perspective&lt;/li&gt;
&lt;li&gt;Menu: Pydev → Start Debug Server&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Start debug server" src="/blog/resources/debug-jython-scripts-en-8.png" title="Start debug server" /&gt;&lt;/p&gt;
&lt;h3&gt;Step 2: Debug Jython Server Scripts&lt;/h3&gt;
&lt;p&gt;Start the test execution of a Jython Server Script in QF-Test&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Once the script containing the pydevd.settrace statement gets executed, the run gets interrupted&lt;/li&gt;
&lt;li&gt;Further control is possible in Eclipse/Liclipse&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Test execution of Jython Server Script in QF-Test" src="/blog/resources/debug-jython-scripts-en-9.png" title="Test execution of Jython Server Script in QF-Test" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The debug server comes into action&lt;/li&gt;
&lt;li&gt;Execution stack is shown in the left part&lt;/li&gt;
&lt;li&gt;The script node is opened (it is a temporary generated file _script…..)
    &amp;ndash; 1) execution pauses right after the pydevd.settrace call
    &amp;ndash; 2) debugger actions can be used to control further execution.&lt;/li&gt;
&lt;li&gt;Variables can be inspected in the right window part&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Debug server" src="/blog/resources/debug-jython-scripts-en-10.png" title="Debug server" /&gt;&lt;/p&gt;
&lt;p&gt;NOTE: PyDev debugger warnings may appear in the terminal, looking like&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pydev debugger: Unable to find real location for: C:\Users\YourName\AppData\Roaming\qfs\QF-Test\edit_ms.py.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but they are not supposed to do any harm, just warnings.&lt;/p&gt;
&lt;h3&gt;Step 3: Debug Jython SUT Scripts&lt;/h3&gt;
&lt;p&gt;Especially SUT Scripts are meant to be small and simple in order to influence the SUT as little as possible. All complex or time-consuming task shall be done within server scripts. If you pause an SUT Script in the Debugger this effectively puts your whole SUT-Appliction on hold, which may change its behavior or even crash.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there is really the need to debug SUT scripts as well with remote debugging, the pysrc directory need to be added to python.path of the SUT than as well.&lt;/li&gt;
&lt;li&gt;You will need to adapt the start command of your SUT respectively.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As an example you can adapt the start command of the Swing CarConfig in the &lt;strong&gt;CarConfig.cmd&lt;/strong&gt; file in the demo/carconfig directory in your Qf-Test distribution and add a respective python path parameter:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;%JAVA_BIN% -cp "../../qflib/qfdemo.jar;../../qflib/qflib.jar;../../lib/looks.jar;../../lib/jgraphx.jar" -Dpython.path=" T:\common\eclipse\eclipse-4.11-64\plugins\org.python.pydev.core_7.2.1.201904261721\pysrc " "de.qfs.apps.qftest.demo.multi.CarConfigurator" %*&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After having started the CarConfig demo, you should be able to Debug Jython SUT scripts for this client.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The usage of Eclipse/Liclipse IDE for editing and debugging Jython code in QF-Test can be a very powerful and helpful enhancement for handling more complex scripts.&lt;/p&gt;
&lt;p&gt;Groovy or JavaScript code cannot be debugged via PyDev or similar, therefore you are unfortunately still limited to verification by inspection and print statements.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>The license server was shut down and still uses only the old license</title><link href="https://www.qftest.com/en/blog/article/the-license-server-was-shut-down-and-still-uses-only-the-old-license.html" rel="alternate"/><published>2019-11-05T00:00:00+01:00</published><updated>2019-11-05T00:00:00+01:00</updated><author><name>Mike Schmidt</name></author><id>tag:www.qftest.com,2019-11-05:/en/blog/article/the-license-server-was-shut-down-and-still-uses-only-the-old-license.html</id><summary type="html">&lt;p&gt;OK, this is a really special case, but with the increasing use of the QF-Test license server this case is happening more often. The license server just has to be reachable by all QF-Test instances, which use a compatible client license for the server license. When starting, they ask explicitly just this license server if the requested license is still available.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;OK, this is a really special case, but with the increasing use of the QF-Test license server this case is happening more often.&lt;/p&gt;
&lt;p&gt;Before I start, I have to mention a few things.&lt;/p&gt;
&lt;h2&gt;What does a license server make special?&lt;/h2&gt;
&lt;p&gt;QF-Test is able to manage itself normally without any license server. QF-Test is communicating with the other active QF-Test instances when it is started to check, if there is at least one license left for the new starting QF-Test instance.&lt;/p&gt;
&lt;p&gt;This only works within a subnet.&lt;/p&gt;
&lt;p&gt;Now the license server comes into play. It just has to be reachable by all QF-Test instances, which use a compatible client license for the server license.&lt;br /&gt;
 When starting, they ask explicitly just this license server if the requested license is still available.&lt;/p&gt;
&lt;p&gt;In this way you can use the requested licenses beyond the borders of the subnet and the location effectively and dynamically. Moreover, the license upgrade has only to be installed on the server. The QF-Test installations with the client license stay as they are. And this also reduces the effort of course.&lt;/p&gt;
&lt;h2&gt;How do I update the license of the license server with a license upgrade?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;For this there are two possibilities:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Simply start QF-Test at a machine with the client license and perform the license upgrade for the server by the use of the user interface and &amp;#8220;Extras&amp;#8221; → &amp;#8220;License server administration…&amp;#8221; like shown in the screenshot below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="License server administration" src="/blog/resources/license-server-administration.png" title="License server administration" /&gt;&lt;/p&gt;
&lt;p&gt;Alternatively there is also the possibility to shut down the license server, to change the license file and start it up again&lt;/p&gt;
&lt;h2&gt;When does the title&amp;#8217;s case occur now?&lt;/h2&gt;
&lt;p&gt;If you choose the alternative way (method 2) and you run the license server on Windows you can fall into a trap.&lt;/p&gt;
&lt;p&gt;If you want to shut down the license server with the command &lt;code&gt;"qftest -batch -licenseserver.shutdown"&lt;/code&gt;, this is formally correct. But if there is an error message you do not receive it in this case. In contrast to Linux, you have to use &lt;code&gt;"qftestc.exe"&lt;/code&gt; instead of &lt;code&gt;"qftest.exe"&lt;/code&gt; if Windows is used, when messages should be shown within the console. Due to Microsoft Windows’s implementation this is technically not solvable in a different way.&lt;/p&gt;
&lt;p&gt;So instead of &lt;code&gt;"qftest -batch -licenseserver.shutdown"&lt;/code&gt; just run it with the command &lt;code&gt;"qftestc"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If this is done, you receive the message that the password was not accepted.&lt;/p&gt;
&lt;p&gt;Yes, this is usually the problem&amp;#8230; The server is saved with a password, which also has to be inserted additionally when opening it.&lt;/p&gt;
&lt;p&gt;So just use the additional parameter &lt;code&gt;"-licenseserver -password"&lt;/code&gt; with the appropriate password and then the license server is shut down.&lt;/p&gt;
&lt;h3&gt;Example&lt;/h3&gt;
&lt;p&gt;enter&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qftestc -batch -licenseserver.shutdown -licenseserver.password secret&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;in the command prompt respectively&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;amp; qftestc -batch "-licenseserver.shutdown" "-licenseserver.password" secret&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;in the PowerShell console.&lt;/p&gt;
&lt;h3&gt;For the sake of completeness&lt;/h3&gt;
&lt;p&gt;When it is started again there should be no message that the port of the license server is occupied. If the message comes up, the shut down did not work. In this very rare case, you should use the task-manager of Windows to end the associated QF-Test as well as its Java process.&lt;/p&gt;</content><category term="blog"/><category term="license"/></entry><entry><title>Really a “Great Place to Work”? – An internship at QFS</title><link href="https://www.qftest.com/en/blog/article/really-a-great-place-to-work-an-internship-at-qfs.html" rel="alternate"/><published>2019-10-28T00:00:00+01:00</published><updated>2019-10-28T00:00:00+01:00</updated><author><name>Carolin Tutsch</name></author><id>tag:www.qftest.com,2019-10-28:/en/blog/article/really-a-great-place-to-work-an-internship-at-qfs.html</id><summary type="html">&lt;p&gt;You are interested in QF-Test, you already have a trial version and/or want to know more about this company? Most of the time you can only catch a superficial glimpse of the companies on their websites. How the employees are and what it&amp;#8217;s like to work in this company is not really known. You may find an article or an award. Fantastic. But what is behind this appearance? I spent three weeks at QFS as an intern and would like to give you a little insight into the company behind the product.&lt;/p&gt;</summary><content type="html">&lt;p&gt;You are interested in QF-Test, you already have a trial version and/or want to know more about this company? Most of the time you can only catch a superficial glimpse of the companies on their websites. How the employees are and what it&amp;#8217;s like to work in this company is not really known. You may find an article or an award. Fantastic. But what is behind this appearance? I spent three weeks at QFS as an intern and would like to give you a little insight into the company behind the product.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Carolin Tutsch" src="/blog/resources/carolin_tutsch.jpg" title="Carolin Tutsch" /&gt;&lt;/p&gt;
&lt;p&gt;A friendly salutation with &amp;#8220;Hello&amp;#8221; should not be missing. It is sad that today lots of people forget to do this, but at QFS it is still present that everyone greets everyone. Incidentally, this promotes the good working atmosphere and makes most people happy immediately.&lt;/p&gt;
&lt;p&gt;I was aware that I would like to collect more experience in the field of business computer science before I start the studies. By prior circumstances I applied short-term at QFS and at the beginning of September I was happy to be invited to an application conversation. The conversation with Dr. Martina Schmid, Karlheinz Kellerer and Michael Höber was positive. They have spent lots of time to get to know me and discussed the further procedure with me. Because of that, the next day was on an unexpected way my first day at work at QFS and each spoke to the other by their first name.&lt;/p&gt;
&lt;p&gt;At the first day of work I was really excited that I got the task to edit the subtitles of a YouTube video. Of course my work was checked and corrected and I have got constructive feedback from which I could learn well. Most of all surprised me, that I was allowed to attend a meeting. This is a very exciting experience for an intern. After the meeting they immediately asked if I had any questions to the technical terms or at all.&lt;/p&gt;
&lt;p&gt;Next day was home-office-day. It was awesome that they gave me a task that filled the day and was controlled on the next day. Because there was a bit wrong, I was glad that I had the opportunity to correct it myself and to learn from it actively.&lt;/p&gt;
&lt;p&gt;I was happy to see that everyone was included in the meal and kicker right from the beginning.&lt;/p&gt;
&lt;p&gt;After the first few days of getting to know each other, the atmosphere thawed.&lt;/p&gt;
&lt;p&gt;In general, it was noticeable that the working atmosphere is relaxed and laid back, yet constructive and everyone enjoys working there.&lt;/p&gt;
&lt;p&gt;Market research are very interesting especially for intern to know which differences on websites of diverse companies are existing on the internet. This is not only of interest if you are purely working with websites. I guess lots of you know that &amp;ndash; even when you are working in a different part of the company, where you do not work with websites that much &amp;ndash; you want to know how your company presents itself on the internet and also how the presentation is in relation to other companies.&lt;/p&gt;
&lt;p&gt;The first week was over too fast and I noticed with pleasure, while sitting on my workplace on Monday, that in between there were private conversations about the last weekend. After this short break, the work was even easier and faster done.&lt;/p&gt;
&lt;p&gt;During the week a translation had to be done and after mentioning that I also enjoy working with languages, I soon had a second one to do. Yes! ;-)&lt;/p&gt;
&lt;p&gt;I have never really been without work and was never bored. The various tasks were given to me at a comfortable interval. Every time when I had finished something, I have been already asked if I have something to do, I was given a new work to do or I just asked somebody.&lt;/p&gt;
&lt;p&gt;From time to time I had chances to see what the different areas of responsibilities of QFS are doing and was not tied to one department.&lt;/p&gt;
&lt;p&gt;There was also the opportunity of working with their test tool QF-Test.&lt;/p&gt;
&lt;p&gt;Do know what the product of this company exactly does, but also to know what work is behind the product, is a great experience. I am sure that I can definitely benefit from it in the future.&lt;/p&gt;
&lt;p&gt;My wish of having QFS as my training partner during the dual study course, could not be realized for reasons of capacity, but I am so grateful that I had the opportunity to be a part of this friendly, family and very human company for this three weeks.&lt;/p&gt;
&lt;p&gt;Are there no negative experiences? Of course there are discussions between colleagues sometimes, but this is just human. There is no way of being always on one opinion. So the answer is: No. I have no negative experience with QFS. And everyone is always at your side in case of problems, be it professional or private, and support you.&lt;/p&gt;
&lt;p&gt;Yes, you have read right. I was warmly received and QFS absolutely is a &amp;#8220;Great Place to Work&amp;#8221; without any doubt.&lt;/p&gt;
&lt;p&gt;I wish you a wonderful day and hope that you are also able to work in such a wonderful setting.&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Read and write Excel files from QF-Test</title><link href="https://www.qftest.com/en/blog/article/read-and-write-excel-files-from-qf-test.html" rel="alternate"/><published>2019-10-08T00:00:00+02:00</published><updated>2019-10-08T00:00:00+02:00</updated><author><name>Ute Erler</name></author><id>tag:www.qftest.com,2019-10-08:/en/blog/article/read-and-write-excel-files-from-qf-test.html</id><summary type="html">&lt;p&gt;If you want to use data from an MS Excel file in your QF-Test tests or want to write test results to MS Excel files then go on reading.&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you want to use data from an MS Excel file in your QF-Test tests or want to write test results to MS Excel files then go on reading.&lt;/p&gt;
&lt;p&gt;Attached to the article you will find a test suite, QFSExcel.qft, containing examples and an accompanying Excel file, &amp;#8216;Data.xlsx&amp;#8217;. To run the examples, copy both files into the same directory and open and run the test suite with QF-Test.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="/blog/resources/QFSExcel.qft"&gt;QFSExcel.qft&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="/blog/resources/Data.xlsx" title="Open external link in new window"&gt;Data.xslx&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Reading Excel data&lt;/h2&gt;
&lt;h3&gt;1. The ‘Data driver’ node&lt;/h3&gt;
&lt;p&gt;The easiest way to read Excel data is via the ‘Data driver’ node. The Excel file contains a number of data sets (one per row or also per column). The data driver reads the data sets and generates as many loops as there are data sets. In each loop all test cases of the test-set will be executed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Datadriver" src="/blog/resources/excel-2-datadriver-en.png" title="QF-Test Datadriver" /&gt;&lt;/p&gt;
&lt;p&gt;The effect of this structure is that the test case(s) will be run once for each data set.&lt;/p&gt;
&lt;p&gt;The ‘Data driver’ node can be used within ‘Test set’ and ‘Test-step’ nodes.&lt;/p&gt;
&lt;h3&gt;2. The ‘Excel file’ node alone&lt;/h3&gt;
&lt;p&gt;If you want to use MS Excel data in a different way you can use the ‘Excel file’ node on its own to read the data into a group variable.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Node in QF-Test" src="/blog/resources/excel-3-node-en.png" title="Node in QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;In the above example the Excel data are read into a variable group called ‘data’.&lt;/p&gt;
&lt;p&gt;You can list all variables of the group using the procedure ‘listAllMembersOfPropertyGroup’ from the package qfs.utils.variables of the QF-Test standard procedure library qfs.qft.&lt;/p&gt;
&lt;p&gt;The syntax for accessing a single variable in a group is ${groupname:variable}. &lt;br /&gt;
The syntax of the variable for referencing an Excel cell is &amp;#8216;columnname.index&amp;#8217;, respectively &amp;#8216;rowname.index&amp;#8217;. This said, the complete variable syntax for a cell, when the data sets are organized in rows, would be ${groupname:columnname.index}.&lt;br /&gt;
The names have to be in the first row, resp. column, of the data sheet. The index starts with the following row resp. column. All indexes are zero-based. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Excel data for QF-Test" src="/blog/resources/excel-4-excel-data-en.png" title="Excel data for QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;So, the variable referencing cell B4 would be &lt;code&gt;${data:Test-case.2}&lt;/code&gt;, where ‘data’ is the group name.&lt;/p&gt;
&lt;p&gt;The ‘Excel file’ node also generates a variable for the number of data sets read, &lt;code&gt;${group:size}&lt;/code&gt;, and the number of rows, resp. columns, read, &lt;code&gt;${group:totalsize}&lt;/code&gt;. The two may differ if the file contains empty rows or columns between the data sets.&lt;/p&gt;
&lt;h2&gt;Writing Excel data&lt;/h2&gt;
&lt;h3&gt;1. Using the sample procedures&lt;/h3&gt;
&lt;p&gt;The third example in the attached test suite copies an existing Excel  file to a new one and adds a result value to each data set.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Write Excel" src="/blog/resources/excel-5-write-excel-en.png" /&gt;&lt;/p&gt;
&lt;p&gt;The procedure ‘createWorkBookFromFile’ reads an existing MS Excel file into a Jython variable using the Excel API. The next procedure assigns an Excel sheet to another global Jython variable. One procedure shows how to use the Excel API in order to read a cell value. Another procedure writes a value to a cell. The last procedure writes the work book to a new Excel file. All procedures make use of global Jython variables in order to pass the complex data structures for the work book and the data sheet between the procedures.&lt;/p&gt;
&lt;h3&gt;2. In-depth details and scripting possibilities&lt;/h3&gt;
&lt;p&gt;I used the Excel API of Apache. There are far more methods to the Excel API than used here. So feel free and use the sample scripts as a basis and adapt them to your needs. I found &lt;a href="https://poi.apache.org/components/spreadsheet/quick-guide"&gt;https://poi.apache.org/components/spreadsheet/quick-guide&lt;/a&gt; quite helpful. You can use the API methods in QF-Test Server Scripts.&lt;/p&gt;
&lt;p&gt;Explanation to the script in the first procedure:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Excel API Apache" src="/blog/resources/excel-6-excel-api-apache.png" title="QF-Test Excel API Apache" /&gt;&lt;/p&gt;
&lt;p&gt;Lines 1 and 2 import the required modules for the Excel API (org.apache.poi.ss.usermodel) and the Java file handling (&lt;a href="https://docs.oracle.com/javase/8/docs/api/java/io/package-summary.html"&gt;java.io&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Line 4 defines ‘wrkbook’ as a global Jython variable. Thus the ‘wrkbook’ variable can also be used by the other Jython scripts.&lt;/p&gt;
&lt;p&gt;Lines 6 reads the Excel file parameter passed to the QF-Test procedure using the method ‘lookup’ of the QF-Test module ‘rc’.&lt;/p&gt;
&lt;p&gt;Lines 7 to 10 read the Excel file. If it does not exist an exception is thrown.&lt;/p&gt;
&lt;p&gt;Line 12 assigns the work book to the variable using a method of the Excel API.&lt;/p&gt;
&lt;p&gt;Line 14 uses a Java method for closing the file.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Execute test cases and sets with a certain tag</title><link href="https://www.qftest.com/en/blog/article/execute-test-cases-andor-test-sets-annotated-with-a-certain-tag.html" rel="alternate"/><published>2019-10-01T00:00:00+02:00</published><updated>2019-10-01T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-10-01:/en/blog/article/execute-test-cases-andor-test-sets-annotated-with-a-certain-tag.html</id><summary type="html">&lt;p&gt;Large test projects may consist out of several thousands of test cases. Often executing them all would need a considerable amount of time, even if multiple machines are used in order to replay the different tests. The solution are tags.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Large test projects may consist out of several thousands of test cases. Often executing them all would need a considerable amount of time, even if multiple machines are used in order to replay the different tests.&lt;/p&gt;
&lt;p&gt;In order to develop such large test projects, it is often useful to store the different test sets/cases in different files. For example we may create one file that contains all tests for feature A and another file that contains all tests for feature B. Nevertheless it may still be a good idea to annotate the different tests with tags. &lt;/p&gt;
&lt;p&gt;Tags like &amp;#8220;feature1&amp;#8221;, &amp;#8220;feature2&amp;#8221;, &amp;#8220;fastRunningTest&amp;#8221;, &amp;#8220;slowTest&amp;#8221;, &amp;#8220;importantTest&amp;#8221;, &amp;#8220;oftenFailingTest&amp;#8221; and so on make it possible to run a dedicated subset of all test cases. For example this makes it possible to run only those test cases tagged with &amp;#8220;oftenFailingTest&amp;#8221; and &amp;#8220;importantTest&amp;#8221;.&lt;/p&gt;
&lt;p&gt;The &amp;#8220;Condition&amp;#8221; field of a test case/test-set node can be used to assign tags to any test case/test-set. For example if you want to assign the three tags tag1, tag2 and tag3 to a test case / test-set you can use the following syntax: &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;quot;&amp;quot;&amp;quot;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;tagsToExecute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;&amp;quot;&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;==&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;&amp;quot;&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;len(set([&amp;quot;tag1&amp;quot;,&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;tag2&amp;quot;,&lt;span class="w"&gt; &lt;/span&gt;&amp;quot;tag3&amp;quot;]).intersection([x.strip()&lt;span class="w"&gt; &lt;/span&gt;for&lt;span class="w"&gt; &lt;/span&gt;x&lt;span class="w"&gt; &lt;/span&gt;in&lt;span class="w"&gt; &lt;/span&gt;rc.lookup(&amp;quot;tagsToExecute&amp;quot;,&lt;span class="w"&gt; &lt;/span&gt;expand=False).split(&amp;quot;,&amp;quot;)]))&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;=&lt;span class="w"&gt; &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This condition will advice QF-Test to execute the test case/set when one of the following conditions are met:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;tagsToExecute&lt;/code&gt; variable is not set or empty.&lt;/li&gt;
&lt;li&gt;The test case/test-set is annotated with a tag that is also listed in the &lt;code&gt;tagsToExecute&lt;/code&gt; variable (comma-separated list of tags).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means in order to execute only those tests annotated with tag1, the variable &lt;code&gt;tagsToExecute&lt;/code&gt; needs to be set to &lt;code&gt;tag1&lt;/code&gt;. In order to execute those tags either annotated with &lt;code&gt;tag1&lt;/code&gt; or &lt;code&gt;tag2&lt;/code&gt; the variable  &lt;code&gt;tagsToExecute&lt;/code&gt; needs to be set to &lt;code&gt;tag1&lt;/code&gt;,&lt;code&gt;tag2&lt;/code&gt; and in order to execute all tests annotated with tag1, tag2 or tag3 the variable &lt;code&gt;tagsToExecute&lt;/code&gt; needs to be set to &lt;code&gt;tag1&lt;/code&gt;,&lt;code&gt;tag2&lt;/code&gt;,&lt;code&gt;tag3&lt;/code&gt;. &lt;/p&gt;
&lt;h2&gt;Remarks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;When calling QF-Test from command line, it is possible to set a  &lt;code&gt;tagsToExecute&lt;/code&gt; variable via the -variable command line argument (e.g. -variable tagsToExecute=tag1,tag2), see&lt;br /&gt;
&lt;a href="https://www.qftest.com/doc/manual/en/contents.html#toc_sec_execution" title="Command line arguments and exit codes"&gt;Command line arguments and exit codes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For better readability, it is allowed to add spaces before and after the comma. So in order to execute the tests annotated with &lt;code&gt;tag1&lt;/code&gt; or &lt;code&gt;tag2&lt;/code&gt; the variable  &lt;code&gt;tagsToExecute&lt;/code&gt; may be set to&lt;br /&gt;
&lt;code&gt;tag1&lt;/code&gt;,&lt;code&gt;tag2&lt;/code&gt; or&lt;br /&gt;
&lt;code&gt;tag1&lt;/code&gt;, &lt;code&gt;tag2&lt;/code&gt; or even  &lt;code&gt;tag1&lt;/code&gt; , &lt;code&gt;tag2&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>The impact of geometry on component recognition</title><link href="https://www.qftest.com/en/blog/article/the-impact-of-geometry-on-component-recognition.html" rel="alternate"/><published>2019-09-19T00:00:00+02:00</published><updated>2019-09-19T00:00:00+02:00</updated><author><name>Gregor Schmid</name></author><id>tag:www.qftest.com,2019-09-19:/en/blog/article/the-impact-of-geometry-on-component-recognition.html</id><summary type="html">&lt;p&gt;This is a short article about why QF-Test records geometry information for components, what impact these values have and how to change them either after or during recording.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;&amp;#8230;and what to do about it&lt;/h2&gt;
&lt;p&gt;This is a short article about why QF-Test records geometry information for components, what impact these values have and how to change them either after or during recording .&lt;/p&gt;
&lt;p&gt;For the interested, the whole component recognition algorithm is explained in the technical details of the QF-Test manual, section &amp;#8220;&lt;a href="https://www.qftest.com/doc/manual/en/tech_recognition.html#sec_recognition" title="Component recognition"&gt;Component recognition&lt;/a&gt;&amp;#8221;:&lt;/p&gt;
&lt;h3&gt;Here&amp;#8217;s a short summary:&lt;/h3&gt;
&lt;p&gt;Based on the component information stored in the test suite QF-Test determines a match probability for each visible component in the SUT. The component with the highest probability wins, provided that probability is a above a configurable threshold. Otherwise a  ComponentNotFoundException is thrown. Though the algorithm has been fine-tuned many times, it&amp;#8217;s basic concept hasn&amp;#8217;t changed since QF-Test&amp;#8217;s early days and the default settings have proven useful in most cases.&lt;/p&gt;
&lt;p&gt;Geometry is the least relevant part of component recognition, but it may have a significant impact in case the other characteristics are not conclusive.&lt;/p&gt;
&lt;p&gt;The probability for a component starts with a 99.9% match. Then geometry is applied in one of three ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each of the values &amp;ndash; if specified and ignoring X and Y for top-level windows &amp;ndash; is compared to the actual values and a deviation reduces the probability.&lt;/li&gt;
&lt;li&gt;Consequently &amp;ndash; and this is important &amp;ndash; if geometry information in the test suite is removed, all components start out equal, but with a perfect geometry match.&lt;/li&gt;
&lt;li&gt;If any of the geometry values is set to &amp;#8216;-&amp;#8216; the probability is immediately reduced to zero, so again all components start out equal but with zero probability so that a match of any of the other characteristics is required.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Hands-on example&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s work on an example to see first-hand what this means:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In case you have a commercial license that does not include the Swing engine the CarConfig demo will not connect. To work through the example you can start QF-Test in demo mode with the command &lt;code&gt;qftest -license=&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open the Help → &amp;#8220;Explore sample test suites&amp;#8221; menu item and select the first example, &amp;#8220;Swing CarConfig Suite&amp;#8221;.&lt;/li&gt;
&lt;li&gt;To start the SUT expand the Test-set, select the Dependency reference &amp;#8220;dependencies.sutStartedAndReset&amp;#8221; and press [Return] or click the Run toolbar button. The QF-Test CarConfig demo should start up and the record button should become enabled.&lt;/li&gt;
&lt;li&gt;Open a new, empty test suite and record three mouse clicks on the value fields for &amp;#8220;Base price&amp;#8221;, &amp;#8220;Specials price&amp;#8221; and &amp;#8220;Discount&amp;#8221;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="QF-Test Geometry Clicks" src="/blog/resources/geometry-1-clicks.png" /&gt;The recorded components should look like in the following screenshot&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Geometry Recorded nodes" src="/blog/resources/geometry-2-recorded_nodes.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Naturally &amp;ndash; this is a demo application after all &amp;ndash; the recorded component information is totally overqualified. We have a perfect name, explicitly associated label as feature, implicitly determined qfs:label as extra feature plus structure and geometry information that is still fresh and valid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the mouse click on JCarConfigurator.DiscountValue, activate &amp;#8216;Replay as &amp;#8220;hard&amp;#8221; event&amp;#8217; and run it. The click should be spot-on.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a real-world situation, name and feature information may be missing, the label might have change and structure and geometry information deteriorate as the layout of the components in the SUT changes or components get added or removed.&lt;/p&gt;
&lt;h2&gt;Playing with component information to learn about the effects&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s modify our recorded information in several steps to learn about their effect on component recognition:&lt;/p&gt;
&lt;h3&gt;1. No name&lt;/h3&gt;
&lt;p&gt;Remove the name &amp;#8220;DiscountValue&amp;#8221; from the component information, then replay the mouse click.&lt;/p&gt;
&lt;p&gt;→ Still good. To see how good, open the options dialog via the Edit-&amp;gt;Options menu item, select Run logs-&amp;gt;Content in the tree and set the option &amp;#8220;Log level for the SUT&amp;#8221; to &amp;#8220;All messages&amp;#8221;. Don&amp;#8217;t forget to reset this option at the end so that your run log don&amp;#8217;t grow needlessly.&lt;/p&gt;
&lt;p&gt;Then replay the mouse click and open the run log via &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;L&lt;/kbd&gt;.&lt;/p&gt;
&lt;p&gt;Expand the nodes via &lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;Right Arrow&lt;/kbd&gt; and select &amp;#8220;Looking for matching top-level components&amp;#8230;&amp;#8221;. The information shown there is not user-friendly. Its main goal is to provide information for QFS support when helping to resolve component recognition issues.&lt;/p&gt;
&lt;p&gt;Scroll down to the bottom, go back a little and you&amp;#8217;ll find something close to the following:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;...&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Calculating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;probability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;


&lt;span class="nv"&gt;javax&lt;/span&gt;.&lt;span class="nv"&gt;swing&lt;/span&gt;.&lt;span class="nv"&gt;JTextField&lt;/span&gt;[&lt;span class="nv"&gt;DiscountValue&lt;/span&gt;,&lt;span class="mi"&gt;239&lt;/span&gt;,&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="nv"&gt;x30&lt;/span&gt;,&lt;span class="nv"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;javax&lt;/span&gt;.&lt;span class="nv"&gt;swing&lt;/span&gt;.&lt;span class="nv"&gt;plaf&lt;/span&gt;.&lt;span class="nv"&gt;basic&lt;/span&gt;.&lt;span class="nv"&gt;BasicTextUI&lt;/span&gt;$&lt;span class="nv"&gt;UpdateHandler&lt;/span&gt;,&lt;span class="nv"&gt;alignmentX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="nv"&gt;alignmentY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="nv"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,&lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;296&lt;/span&gt;,&lt;span class="nv"&gt;maximumSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,&lt;span class="nv"&gt;minimumSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,&lt;span class="nv"&gt;preferredSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,&lt;span class="nv"&gt;caretColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;javax&lt;/span&gt;.&lt;span class="nv"&gt;swing&lt;/span&gt;.&lt;span class="nv"&gt;plaf&lt;/span&gt;.&lt;span class="nv"&gt;ColorUIResource&lt;/span&gt;[&lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;],&lt;span class="nv"&gt;disabledTextColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;javax&lt;/span&gt;.&lt;span class="nv"&gt;swing&lt;/span&gt;.&lt;span class="nv"&gt;plaf&lt;/span&gt;.&lt;span class="nv"&gt;ColorUIResource&lt;/span&gt;[&lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;,&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;,&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;],&lt;span class="nv"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;,&lt;span class="nv"&gt;margin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;javax&lt;/span&gt;.&lt;span class="nv"&gt;swing&lt;/span&gt;.&lt;span class="nv"&gt;plaf&lt;/span&gt;.&lt;span class="nv"&gt;InsetsUIResource&lt;/span&gt;[&lt;span class="nv"&gt;top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="nv"&gt;left&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;,&lt;span class="nv"&gt;bottom&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;,&lt;span class="nv"&gt;right&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;],&lt;span class="nv"&gt;selectedTextColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;javax&lt;/span&gt;.&lt;span class="nv"&gt;swing&lt;/span&gt;.&lt;span class="nv"&gt;plaf&lt;/span&gt;.&lt;span class="nv"&gt;ColorUIResource&lt;/span&gt;[&lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;],&lt;span class="nv"&gt;selectionColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;javax&lt;/span&gt;.&lt;span class="nv"&gt;swing&lt;/span&gt;.&lt;span class="nv"&gt;plaf&lt;/span&gt;.&lt;span class="nv"&gt;ColorUIResource&lt;/span&gt;[&lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;172&lt;/span&gt;,&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;210&lt;/span&gt;,&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;248&lt;/span&gt;],&lt;span class="nv"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;,&lt;span class="nv"&gt;columnWidth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;,&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,&lt;span class="nv"&gt;horizontalAlignment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;RIGHT&lt;/span&gt;]
&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="nv"&gt;Geometry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;probability&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;.&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nv"&gt;Structure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;matchStructure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;probability&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;penalty&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nv"&gt;Extra&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;feature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;matchExtra&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;feature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;probability&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;penalty&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nv"&gt;Feature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;matchFeature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;probability&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;penalty&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nv"&gt;Combined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;probability&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;acceptableFinal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;probability&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Calculation starts with the &amp;#8220;Geometry probability&amp;#8221; as base value, then structure, extra feature, feature and name are applied &amp;ndash; if available &amp;ndash; as explained in the manual leading to the final probability.&lt;/p&gt;
&lt;h3&gt;2. No feature&lt;/h3&gt;
&lt;p&gt;Remove the feature &amp;#8220;Label: Discount&amp;#8221; from the component information, then replay the mouse click and check the run log as before.&lt;/p&gt;
&lt;p&gt;→ Still good, almost unchanged, just no Feature match:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Geometry probability: 99.9%  
 Structure match  
 Structure probability: 100%, penalty: 100%  
 Extra feature match  
 Extra feature probability: 100%, penalty: 100%  
 Combined probability: 100%, acceptable  
 Final probability: 99%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;3. Changed feature&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s make it more interesting. Undo the last change to restore the label, then edit the value, e.g. by appending &amp;#8220;bla&amp;#8221;, then run again and check the log.&lt;/p&gt;
&lt;p&gt;→ The click is still on target, but the log shows a feature mismatch and the final probability is barely above the minimum probability of 50%:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Geometry probability: 99.9%  
 Structure match  
 Structure probability: 100%, penalty: 100%  
 Extra feature match  
 Extra feature probability: 100%, penalty: 100%  
 Feature mismatch  
 Feature probability: 100%, penalty: 55%  
 Combined probability: 55%, acceptable  
 Final probability: 54.4%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;4. Changed feature, extra feature and structure&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s give it all we&amp;#8217;ve got: Change the class count to 5 to simulate that a previously existing TextField has been removed in the meantime and modify the extra feature qfs:label, e.g. by also appending &amp;#8220;bla&amp;#8221; to the value &amp;#8220;Discount&amp;#8221; as shown in the&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Geometry Modified Info" src="/blog/resources/geometry-3-modified-info.png" /&gt;&lt;/p&gt;
&lt;p&gt;→ Surprisingly the click is still on target and despite 3 mismatch warnings the resulting probability has barely changed:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Geometry probability: 99.9%  
 Structure mismatch index: 2, count: 4  
 Structure probability: 99.9%, penalty: 60%  
 Extra feature mismatch  
 Extra feature probability: 99.9%, penalty: 55%  
 Feature mismatch  
 Feature probability: 99.9%, penalty: 55%  
 Combined probability: 54.9%, acceptable  
 Final probability: 54.4%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So in this case geometry saves the day, keeps the test running and enables you to recover current, valid component information by right-clicking the component node and selecting &amp;#8220;Update component(s)&amp;#8221;. Go ahead and try it, but then use &amp;#8220;Undo&amp;#8221; to restore the deliberately broken state from the last screenshot.&lt;/p&gt;
&lt;h3&gt;5. Empty geometry &amp;ndash; match everything&lt;/h3&gt;
&lt;p&gt;Now lets finally start messing with the geometry values. As we have a perfect geometry match anyway, things shouldn&amp;#8217;t change if we remove the geometry values X, Y, Width and Height entirely (cf. summary at the beginning).&lt;/p&gt;
&lt;p&gt;→ &lt;strong&gt;Oops!&lt;/strong&gt; Now the click goes to the Base price value. Looking at the run log we see that all four TextFields now end up with the same final probability of 54.4% which makes sense as all start with the same geometry probability and have the same mismatches. For lack of further options QF-Test simply chooses the first one.&lt;/p&gt;
&lt;p&gt;This shows that removing geometry values entirely is inviting disaster in the form of false positive matches. The ability to leave individual geometry fields empty is still useful though. For example you can target the individual TextFields based on geometry only by setting the Y field to 30, 60, 90 or 120 respectively.&lt;/p&gt;
&lt;h3&gt;6. Geometry &amp;#8220;-&amp;#8221; &amp;ndash; match nothing&lt;/h3&gt;
&lt;p&gt;False positive matches are difficult to analyze so we may want to avoid a geometry based match altogether. Set one or all of the X, Y, Width and Height attribute to just &amp;#8216;-&amp;#8216;.&lt;/p&gt;
&lt;p&gt;→ OK, we&amp;#8217;ve now broken things to the point where we get a ComponentNotFoundException.  The details in the run log show that all candidates now have zero probability as expected.&lt;/p&gt;
&lt;h3&gt;7. Restore structure match&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s restore the structure information by changing Class count back to 4.&lt;/p&gt;
&lt;p&gt;→ No luck&lt;/p&gt;
&lt;p&gt;The run log shows that the structure match would have brought us up to an acceptable 70% match, but the feature and extra feature mismatch bring it down again to 38.5% which is below the threshold.&lt;/p&gt;
&lt;h3&gt;8. Restore feature&lt;/h3&gt;
&lt;p&gt;Undo to break structure again and fix the feature instead, setting it back to Label: Discount&lt;/p&gt;
&lt;p&gt;→ The click is back on target&lt;/p&gt;
&lt;p&gt;From the run log we see that (in the absence of names) the feature has the highest relevance and is sufficient to raise the probability above the threshold in spite of the other mismatches.&lt;/p&gt;
&lt;h3&gt;9. Roll your own&lt;/h3&gt;
&lt;p&gt;Go ahead and experiment with restoring structure and/or extra feature information while also removing the higher relevance attributes feature and/or extra feature. You&amp;#8217;ll see that correct structure info alone is sufficient when not hindered by some other mismatch.&lt;/p&gt;
&lt;h2&gt;Generic components&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The manual chapter &amp;#8220;How to achieve robust component recognition&amp;#8221; is generally worth reading. Section A&lt;a href="https://www.qftest.com/doc/manual/en/bp_componentrecognition.html#sec_bpCRGeneric"&gt;voiding recording every component or using generic components&lt;/a&gt; () explains the concept of generic components which we are going to need for the following.&lt;/p&gt;
&lt;p&gt;So let&amp;#8217;s apply the knowledge gained to creating a generic component based on just the qfs:label extra feature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select TextField JCarConfigurator.DiscountValue , copy, select Window JCarConfigurator and paste. Select &amp;#8220;Make QF-Test IDs unique&amp;#8221; to prevent ID conflicts, then change the QF-Test ID of the copied node to genericTextField.&lt;/li&gt;
&lt;li&gt;Set geometry to &amp;#8216;-&amp;#8216;, remove feature, structure and all extra features except qfs:label and set the value of the qfs:label extra feature to $(label). The result should look as shown below:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="QF-Test Geometry Generic" src="/blog/resources/geometry-4-generic.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To make use of that component, create a Test-step under Extras with a variable named &amp;#8220;label&amp;#8221;, value &amp;#8220;Discount&amp;#8221;. Copy the mouse click node on JCarConfigurator.DiscountValue into the Test-step and change the QF-Test component ID to genericTextField. It should work right away.&lt;/li&gt;
&lt;li&gt;And as for why and where this can be useful: Add a data driver node to the Test-step and inside that a Data table, name &amp;#8220;label&amp;#8221;, column &amp;#8220;label&amp;#8221;, four rows with values &amp;#8220;Base price&amp;#8221;, &amp;#8220;Specials price&amp;#8221;, &amp;#8220;Discount&amp;#8221; and &amp;#8220;Final price&amp;#8221; as shown below, then run the whole test step&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="QF-Test Geometry Data table" src="/blog/resources/geometry-5-datatable.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Geometry values in recorded components are useful in combination with other characteristics. If you tend to work mostly with recorded components, just keep it as is. In case most of the recorded components have no reliable names or other features you&amp;#8217;ll get best result by ensuring fixed window sizes upon SUT startup.&lt;/li&gt;
&lt;li&gt;Removing all geometry values is a bad idea because that can lead to false-positive matches when features change. To take geometry out of the picture, set the values to &amp;#8216;-&amp;#8216; instead.&lt;/li&gt;
&lt;li&gt;Generic components should always use geometry &amp;#8216;-&amp;#8216; unless they are explicitly based on coordinates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you feel that disabling geometry via &amp;#8216;-&amp;#8216; could improve your tests you may wonder how to implement that efficiently. So I&amp;#8217;ll finish this article by looking at how to mass-replace geometry values with &amp;#8216;-&amp;#8216; and provide an as-yet-undocumented tool to make sure new components get recorded that way.&lt;/p&gt;
&lt;h2&gt;Mass-changing recorded geometry values&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Before performing any kind of mass operations, always make sure that you test suites are properly saved and backed up, ideally in a version control system like git!&lt;/p&gt;
&lt;p&gt;Mass-replacement in a single test suite is simple: Select the &amp;#8216;Windows and components&amp;#8217; node, press &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;H&lt;/kbd&gt; to open the replace dialog (menu Edit-&amp;gt;Replace&amp;#8230;), make sure to use the advanced dialog, clicking the &amp;#8216;&amp;gt;&amp;gt;&amp;#8217; icon if necessary and set the values as shown:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Geometry Replace" src="/blog/resources/geometry-5-replace.png" /&gt;&lt;/p&gt;
&lt;p&gt;Be sure to check &amp;#8220;Match whole attribute&amp;#8221; and &amp;#8220;Use regular expressions&amp;#8221;, then click &amp;#8220;Replace all&amp;#8221;.&lt;/p&gt;
&lt;p&gt;For aesthetic reasons you may want to repeat the same for attributes X, Y and Height, but it is not necessary to achieve the wanted effect.&lt;/p&gt;
&lt;p&gt;You can do the same for all test suites in a project by opening the replace dialog from the project view and setting the scope of the operation to &amp;#8220;Project&amp;#8221;. There&amp;#8217;s the small problem that X and Y attributes also apply to mouse event nodes, but for those &amp;#8216;-&amp;#8216; is not a legal value so no harm should be done.&lt;/p&gt;
&lt;p&gt;In case you don&amp;#8217;t want to drop geometry entirely but change it only based on some condition, e.g. only for components of class TextField, please see &lt;a href="https://www.qftest.com/doc/manual/en/user_gui.html#usec_tunesearch"&gt;https://www.qftest.com/doc/manual/en/user_gui.html#usec_tunesearch&lt;/a&gt; about how to combine searching, setting marks and limiting the replace operation to those marks.&lt;/p&gt;
&lt;h2&gt;Change the way geometry gets recorded&lt;/h2&gt;
&lt;p&gt;Finally, here&amp;#8217;s the promised resolver that can modify geometry settings while recording. It is based on an internal API that is sometimes used by our support for fine-tuning highly individual needs. It is too complex for general use but serves well for this case. To enable this resolver, add the following Groovy SUT script node after the &amp;#8216;Wait for client to connect&amp;#8217; node for your SUT:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getElementInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setWidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_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;info&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;resolvers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;drop geometry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;getElementInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To limit the effects of the resolver to components of one or more specific classes, change the addResolver method accordingly, e.g.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;resolvers.addResolver(&amp;quot;drop geometry&amp;quot;, this.&amp;amp;getElementInfo, &amp;quot;TextField&amp;quot;, &amp;quot;MenuItem&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can also filter by &lt;code&gt;info.getClazz()&lt;/code&gt;, &lt;code&gt;info.getName()&lt;/code&gt; or &lt;code&gt;info.getFeature()&lt;/code&gt; in the resolver method. As indicated above, for highly individual needs please contact our support.&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>Software Testing Thesis</title><link href="https://www.qftest.com/en/blog/article/software-testing-thesis.html" rel="alternate"/><published>2019-08-06T00:00:00+02:00</published><updated>2019-08-06T00:00:00+02:00</updated><author><name>Quality First Software</name></author><id>tag:www.qftest.com,2019-08-06:/en/blog/article/software-testing-thesis.html</id><summary type="html">&lt;p&gt;We want to help students in writing their software testing thesis, we decided to start a reference list for relevant testing topics.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We want to help students in writing their software testing thesis. We were also once students and we know how valuable any help is. That’s why we have decided to create a list of references list for relevant testing topics. We provide here mainly literature from books, because web links are hard to maintain, since they often lead to 404 Errors after some years.&lt;/p&gt;
&lt;p&gt;We offer &lt;strong&gt;paid software testing internships&lt;/strong&gt;. This includes of course assistance from our developers and testers. &lt;a href="https://services.qftest.com/en/support/contact/?type=jobs"&gt;If you are interested, just contact us&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You may find thesis which include QF-Test on our website in the section &lt;a href="/en/company/references/evaluation-reports.html"&gt;evaluation reports&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For your thesis we will gladly provide you with a &lt;a href="https://services.qftest.com/en/license/request/"&gt;trial license of QF-Test&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Feel free to post further good sources for these topics here to help future students in writing their software testing theses.&lt;/p&gt;
&lt;h2&gt;Software Testing&lt;/h2&gt;
&lt;p&gt;Assassa, Gassy; Mathkour, Hassan; Al-Ghafees, Bander: Automated Software Testing in Educational Environment: A Design of Testing Framework for Extreme Programming, Department of Computer Science, College of Computer and Information Sciences, King Saud University, Saudi Arabia (2006)&lt;/p&gt;
&lt;p&gt;Belorustes, Vladimir: Efficient Configuration Test Automation using Virtual Machines, Plaxo, Inc.&lt;/p&gt;
&lt;p&gt;Berner, Stefan; Weber, Roland; Keller, Rudolf: Observations and Lessons Learned from Automated Testing, Zuelke Engineering, AG, Zurich Switzerland (2005)&lt;/p&gt;
&lt;p&gt;Candea, George; Bucur, Stefan; Zamfir, Cristian: Automated Software Testing as a Service, School of Computer and Communication Services, EPFL, Lausanne, Switzerland (2010)&lt;/p&gt;
&lt;p&gt;Dustin, Elfriede; Rashka, Jeff; Paul, John: Automated Software Testing, Introduction, Management, and Performance, Addison Wesley (1999)&lt;/p&gt;
&lt;p&gt;Fewster, Mark; Graham, Dorothy: Software Test Automation, Addison-Wesley (1999)&lt;/p&gt;
&lt;p&gt;Haley, Allen: Development and application of a white box approach to integration testing (1984)&lt;/p&gt;
&lt;p&gt;Hauptmann, Benedikt Dr.: Reducing System Testing Effort by Focusing on Commonalities in Test Procedures. PhD thesis, Technische Universität München (2019)&lt;/p&gt;
&lt;p&gt;Khan, Mohammed Ehmer; Khan, Farmeena: A Comparative Study of White Box, Black Box and Grey Box Testing Techniques (2012)&lt;/p&gt;
&lt;p&gt;Littlewood, Bev: How Good Are Software Reliability Predictions? Software Reliability Achievement and Assessment, Oxford: Blackwell Scientific Publications (1987)&lt;/p&gt;
&lt;p&gt;Memon, Atif M.; Stoffa, Mary Lou: Regression Testing of GUIs (2003)&lt;/p&gt;
&lt;p&gt;McMaster, Scott; Memon; Atif M.: &amp;#8221; An Extensible Heuristic-Based Framework for UI Test Case Maintenance&amp;#8221;. In: Proceedings of the IEEE International Conference on Software Maintenance and Evolution (ICSME). Edmonton, Alberta (Canada) (2009)&lt;/p&gt;
&lt;p&gt;Microsoft Corporation, Designing a Test Environment &lt;a href="https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc778654(v=ws.10)"&gt;https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc778654(v=ws.10)&lt;/a&gt; (2003)&lt;/p&gt;
&lt;p&gt;Myers, Glenford; Sandler, Corey; Badgett, Tom: The art of software testing. New Jersey: John Wiley &amp;amp; Sons, Inc., Hoboken (2012)&lt;/p&gt;
&lt;p&gt;Nedyalkova, Stanislava; Bernardino, Jorge: &amp;#8220;Open Source Capture and Replay Tools Comparison&amp;#8221;. In: Proceedings of the 6th International C* Conference on Computer Science and Software Engineering. Porto (Portugal) (2013)&lt;/p&gt;
&lt;p&gt;Sun, Yahong; Jones, Edward: Specification-Driven Automated Testing of UI-based Java Programs, Meditronic, Inc, and Florida A&amp;amp;M University (2004)&lt;/p&gt;
&lt;p&gt;Paradkar, Amit; M.A. Vouk, K.C. Tai: Specification-based testing using cause-effect graphs (1997)&lt;/p&gt;
&lt;p&gt;Patton, Ron: Software Testing (2nd Edition) (2005)&lt;/p&gt;
&lt;p&gt;Oliveira, Gustavo de; Duarte, Alexandre: A framework for Automated Software Testing on the Cloud, Federal University of Paraiba, Paraiba, Brazil (2010)&lt;/p&gt;
&lt;p&gt;Rafi, Dudekula Mohammed; Moses, Katam Reddy Kiran; Petersen, Kai: Benefits and Limitations of Automated Software Testing: Systematic Literature Review and Practitioner Survey, Blekinge Institute of Technology/Erikson AB School of Computing, Karlskrona, Sweden (2012)&lt;/p&gt;
&lt;p&gt;Wang, Shuang; Offutt, Jeff: Comparison of Unit-Level Automated Test Generation Tools, George Mason University, Fairfax, VA (2009)  &lt;/p&gt;
&lt;h2&gt;UI Testing&lt;/h2&gt;
&lt;p&gt;Adamoli, Andrea et al. &amp;#8221; Automated GUI Performance Testing&amp;#8221;. In: Software Quality Journal 19.4 (Dez. 2011)&lt;/p&gt;
&lt;p&gt;Huang, Si; Cohen, Myra; Memon, Atif M.: &amp;#8220;Repairing UI Test Suites Using a Genetic Algorithm&amp;#8221;. In: Proceedings of the 3rd IEEE International Conference on Software Testing, Verification and Validation (ICST). Washington, DC (USA) (2010)&lt;/p&gt;
&lt;p&gt;Fachrul Pralienka Bani, Muhamad et al.: Visual GUI testing in continuous integration environment (2016)&lt;/p&gt;
&lt;p&gt;Memon, Atif M.: &amp;#8220;Automatically Repairing Event Sequence-Based UI Test Suites for Regression Testing&amp;#8221;. In: ACM Transactions on Software Engineering and Methodology 18.2 (Nov. 2008)&lt;/p&gt;
&lt;p&gt;Nguyen, Bao N. et al.: &amp;#8220;GUITAR: An Innovative Tool for Automated Testing of GUI-driven Software&amp;#8221;. In: Automated Software Engineering 21.1 (März 2014).&lt;/p&gt;
&lt;p&gt;Fachrul Pralienka Bani Muhamad, et al.: Visual GUI testing in continuous integration environment (2016)&lt;/p&gt;
&lt;h2&gt;Manager’s view on UI Tests&lt;/h2&gt;
&lt;p&gt;Kaner, Cem: Avoiding Shelfware: A Managers’ View of Automated UI Testing, &lt;a href="https://www.kaner.com/pdfs/shelfwar.pdf"&gt;https://www.kaner.com/pdfs/shelfwar.pdf&lt;/a&gt;, 2002&lt;/p&gt;
&lt;p&gt;Ramler, Rudolf; Wolfmaier, Klaus: Economic Perspectives in Test Automation: Balancing Automated and Manual Testing with Opportunity Cost, Software Competence Center Hagenberg GmbH, Hagenberg, Austria (2006)&lt;/p&gt;
&lt;p&gt;Mueller, Matthias; Padberg, Frank: About the Return on Investment of Test-Driven Development, University of Karlsruhe, Germany (2017)&lt;/p&gt;
&lt;h2&gt;Software Engineering in general&lt;/h2&gt;
&lt;p&gt;IEEE Standard Glossary of Software Engineering Technology ANSI/IEEE 610.12, IEEE Press (1990)&lt;/p&gt;
&lt;p&gt;ISO/IEC/IEEE. Standard 29119-1: Software and systems engineering &amp;ndash; software testing &amp;ndash; part 1: Concepts and definitions (2013)&lt;/p&gt;
&lt;p&gt;Martin, Robert C.: Clean Code, Prentice Hall (2009)&lt;/p&gt;
&lt;p&gt;McConnell, Steve: Code Complete: A Practical Handbook of Software Construction, Second Edition (2004)&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>Using QF-Test in Continuous Integration Systems</title><link href="https://www.qftest.com/en/blog/article/using-qf-test-in-continuous-integration-systems.html" rel="alternate"/><published>2019-07-11T00:00:00+02:00</published><updated>2019-07-11T00:00:00+02:00</updated><author><name>Thomas Max</name></author><id>tag:www.qftest.com,2019-07-11:/en/blog/article/using-qf-test-in-continuous-integration-systems.html</id><summary type="html">&lt;p&gt;Continuous Integration Systems (CI-Systems) support the teams at their steady work on the various sub-steps of the software development process. The main objective being the improvement of software quality.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last update: 2022-03-30&lt;/p&gt;
&lt;p&gt;Continuous Integration Systems (CI-Systems) are essential for modern software development. They support the teams at their steady work on the various sub-steps of the software development process. The main objective being the improvement of software quality. As an example, with CI systems, you can automate and regularly run the process of compiling and linking the software modules whenever the source code they are based on changes. However, you can trigger any other process by the CI system like setting up metrics, generating documentation or even executing automated tests checking certain functionalities of the software modules. For these automated tests you can use Unit tests on the source code level or QF-Test for functional UI tests.&lt;/p&gt;
&lt;p&gt;&lt;img alt="How continuous integration works." src="/blog/resources/continuous-integration-circle.png" title="How continuous integration works." /&gt;&lt;/p&gt;
&lt;p&gt;The automatic execution of operations within a CI System can either be scheduled for certain times or triggered by certain changes in the software modules. Thus reducing the so-called feedback cycles &amp;ndash; a huge gain with CI systems.&lt;/p&gt;
&lt;p&gt;The system informs you without delay about errors which may have occurred within the single CI operations. For example, when a developer checked in non-compilable source code into the version management system or a UI test reveals an hitherto unknown error in the regression tests started automatically.&lt;/p&gt;
&lt;p&gt;It saves a lot of time and energy if things like that pop up right after applying the change rather than much later during the full nightly build and test.&lt;/p&gt;
&lt;p&gt;There are various types of Continuous Integration Systems with different functionality and ranges of use. Some of the more known systems are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jenkins&lt;/li&gt;
&lt;li&gt;&lt;a href="/en/blog/article/qftest-teamcity-ci-integration.html"&gt;TeamCity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Travis CI&lt;/li&gt;
&lt;li&gt;Bamboo&lt;/li&gt;
&lt;li&gt;Gitlab CI/CD&lt;/li&gt;
&lt;li&gt;CircleCI&lt;/li&gt;
&lt;li&gt;among many others.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;QF-Test can be run in batch mode and therefore be easily be integrated into those systems.&lt;/p&gt;
&lt;p&gt;In order to run a test in batch mode with QF-Test you just open a command line shell and execute the following command:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qftestc.exe -batch -run c:\mysuites\suiteA.qft 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This call will start QF-Test in batch mode and run all the test of test suite suiteA.qft. Subsequently QF-Test will generate the respective QF-Test log and save it to the file system.&lt;/p&gt;
&lt;p&gt;With QF-Test batch mode you can use a great number of parameters. It would be beyond the scope of this blog to explain them all.&lt;/p&gt;
&lt;p&gt;For further information please have a look at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.qftest.com/doc/manual/en/user_execbatch.html#usec_execbatch" title="Test execution in batch mode"&gt;Text execution in batch mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html#sec_commandline" title="Command line arguments"&gt;Command line arguments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Almost all CI systems offer the option to execute command lines as part of the continuous integration process. Just insert the command line mentioned above to start the tests.&lt;/p&gt;
&lt;h2&gt;Jenkins Test automation&lt;/h2&gt;
&lt;p&gt;If you are using Jenkins, life is even easier. QFS provides a plugin you can directly install into the Jenkins environment. It only take two clicks to install the plugin, then you have a UI to configure the QF-Test command line. Additionally to the classic CI model Jenkins supports pipelines.&lt;/p&gt;
&lt;p&gt;Find further information on the plugin here: &lt;a href="https://plugins.jenkins.io/qftest/"&gt;https://plugins.jenkins.io/qftest/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Case study&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Logo Airbus" src="/casestudies/resources/airbus.png" /&gt;&lt;/p&gt;
&lt;p&gt;Nice practical example of a CI chain &amp;ndash; besides faster feedback, better SW quality also time saving. Thank you Mr. Schöning for the exemplary implementation!&lt;br /&gt;
&lt;a href="/en/company/references/case-studies/airbus.html"&gt;go to the detailed case study&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Veni, vidi, variavi – Using Variables</title><link href="https://www.qftest.com/en/blog/article/veni-vidi-variavi-using-variables.html" rel="alternate"/><published>2019-07-10T00:00:00+02:00</published><updated>2019-07-10T00:00:00+02:00</updated><author><name>Daniel Rieth</name></author><id>tag:www.qftest.com,2019-07-10:/en/blog/article/veni-vidi-variavi-using-variables.html</id><summary type="html">&lt;p&gt;There‘s no way around using variables if a test suite has to to be flexible, reusable, and elegant. But where are variables set? Which value of the variable is used if I defined multiple ones at different positions? And how can I find out? This article aims to answer these questions.&lt;/p&gt;</summary><content type="html">&lt;p&gt;There‘s no way around using variables if a test suite has to to be flexible, reusable, and elegant. But where are variables set? Which value of the variable is used if I defined multiple ones at different positions? And how can I find out? This article aims to answer these questions.&lt;/p&gt;
&lt;h2&gt;Which value is used?&lt;/h2&gt;
&lt;p&gt;Variables are saved on two stacks: the primary stack and the secondary stack, also called fallback stack. The second-mentioned is used for default-values, that are only called if there is no value found on the primary stack. Those stacks are not hidden, but can be viewed anytime – e.g. by pausing the test execution or debugging. Also the stack trace is attached to the run log in case of an error/exception &lt;strong&gt;(Fig. 1)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Error ⇒ Stack trace can be found in the run log" src="/blog/resources/Variables_Fig_1_error_stacktrace.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 1&lt;/strong&gt;: Error ⇒ Stack trace can be found in the run log&lt;/p&gt;
&lt;p&gt;When the value of a variable is needed, QF-Test will try to find it in the stack. It will start at the &amp;#8220;top&amp;#8221; of the primary stack, going one layer &amp;#8220;lower&amp;#8221; if it does not find a binding for the variable. The default values on the secondary stack are only taken into account if direct binding is found in the primary stack.&lt;/p&gt;
&lt;p&gt;To illustrate this, take a look at &lt;strong&gt;(Fig. 2)&lt;/strong&gt;. Here, &amp;#8220;Variable1&amp;#8221; was bound to a default value in &amp;#8220;ExampleProcedure&amp;#8221;. However, when this procedure is called, the value is explicitly set to &amp;#8220;not default&amp;#8221;. This binding ranks higher and overwrites the default value on the fallback stack.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The default value of &amp;quot;Variable1&amp;quot; is overwritten by the procedure call" src="/blog/resources/Variables_Fig_2_procedure_call.png" /&gt;
 &lt;strong&gt;Figure 2&lt;/strong&gt;: The default value of &amp;#8220;Variable1&amp;#8221; is overwritten by the procedure call&lt;/p&gt;
&lt;h2&gt;How are variables set?&lt;/h2&gt;
&lt;p&gt;There are many ways to set variables. At Edit → Options → Variables latter can be set, some even before a test suite exists. The stack trace shows which binding (&lt;strong&gt;System, Suite Command line, Globals&lt;/strong&gt;) ranks the highest.&lt;/p&gt;
&lt;p&gt;Every node that has the attribute &amp;#8220;Parameter default values&amp;#8221; allows to set default values on the fallback stack.&lt;/p&gt;
&lt;p&gt;The most obvious choice is the &amp;#8220;Set variable&amp;#8221;-node, of course. But there are also multiple ways to read values from our system under test, like the text in a table cell. The simplest way to achieve this is to record a mouse click on the element that contains the value, then transform the node into a &amp;#8220;Fetch text&amp;#8221;-node. The result of the fetch can be bound to a variable and used later. &lt;strong&gt;(Fig.3)&lt;/strong&gt; shows such a node and its attributes. But what does &amp;#8220;Local variable&amp;#8221; mean?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Text of a table cell is fetched and saved as a local variable" src="/blog/resources/Variables_Fig_3_fetch_text.png" /&gt;
 &lt;strong&gt;Figure 3&lt;/strong&gt;: Text of a table cell is fetched and saved as a local variable&lt;/p&gt;
&lt;p&gt;To keep it short and simple: Global variables exist from their creation to the end of time (that being the end of the QF-Test process or until they are explicitly deleted). Also, they are not just bound to your current test suite. Thats an easy to understand concept, but it also can lead to easy to avoid mistakes, where a variable is unintentionally changed somewhere else or not reset after an earlier test execution.&lt;/p&gt;
&lt;p&gt;In the stack, local variables can be found bound to the node on which they are valid. The respective node can be found by debugging, as shown later. If the variable is set in a test case, for example, its value exists in the scope of this test case and all test steps, sequences etc. within it. After this node is done and disappears from the stack trace, all of the local variables that belonged to it cease to exist. This way side effects in huge and complex test suites can be avoided.&lt;/p&gt;
&lt;p&gt;At last there are scripts. A variable can easily be set in a Server- or SUT-script, as you can see in &lt;strong&gt;(Fig. 4)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Setting and reading variables in a script" src="/blog/resources/Variables_Fig_4_scripts.png" /&gt;
 &lt;strong&gt;Figure 4&lt;/strong&gt;: Setting and reading variables in a script
Up to version 9.0, all QF-Test variables were stored as strings. To process them in scripts or similar, they sometimes had to be converted. This changes with object variables &amp;ndash; see the blog post &lt;a href="/en/blog/article/objects-in-qf-test-variables.html"&gt;Objects in QF-Test variables – an overview&lt;/a&gt; for more information.&lt;/p&gt;
&lt;h2&gt;Which value is used for the variable?&lt;/h2&gt;
&lt;p&gt;It‘s easy to lose track of which variable is assigned to which value when working with default values, multiple nested procedure calls and global variables. Luckily there are multiple possibilities to find out the current value of a variable, two of which are presented here:&lt;/p&gt;
&lt;h3&gt;1. Everyones favorite: &amp;#8220;print debugging&amp;#8221;&lt;/h3&gt;
&lt;p&gt;Simply insert a script with &lt;code&gt;qf.println(rc.lookup("name_of_your_variable"))&lt;/code&gt; to print the value of the variable in the terminal. &lt;code&gt;qf.print&lt;/code&gt; got introduced with version 4.6 and works in every scripting language – for older version use the language-specific &lt;code&gt;print&lt;/code&gt; method. You can log the value by using &lt;code&gt;rc.logMessage&lt;/code&gt;  or &lt;code&gt;rc.logError&lt;/code&gt;. Just be a good boy scout and clean up those print statements after you are done.&lt;/p&gt;
&lt;h3&gt;2. Normal debugging&lt;/h3&gt;
&lt;p&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;F8&lt;/kbd&gt; (Mac: &lt;kbd&gt;⌘&lt;/kbd&gt;+&lt;kbd&gt;⇧&lt;/kbd&gt;+&lt;kbd&gt;B&lt;/kbd&gt;) enables breakpoints in your suite. QF-Test will stop the test execution at those breakpoints and switch to debugging mode. Now, these helpful buttons &lt;strong&gt;(Fig. 5)&lt;/strong&gt; enable you to execute nodes step-by-step and a tab for &amp;#8220;Variables&amp;#8221; showing the current stack trace appears next to the terminal (&lt;strong&gt;Fig. 6)&lt;/strong&gt;. As mentioned before, the highest binding of a variable is its currently used value.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Variables debugger icons" src="/blog/resources/Variables_Fig_5_toolbar.png" /&gt;
 &lt;strong&gt;Figure 5&lt;/strong&gt;:step in (&lt;kbd&gt;F7&lt;/kbd&gt;), step over (&lt;kbd&gt;F8&lt;/kbd&gt;), step-out (&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;F7&lt;/kbd&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img alt="The highest binding of &amp;quot;Variable1&amp;quot; is the currently used one" src="/blog/resources/Variables_Fig_6_variable_view.png" /&gt;
 &lt;strong&gt;Figure 6&lt;/strong&gt;: The highest binding of &amp;#8220;Variable1&amp;#8221; is the currently used one&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>News about the print(…) and println(…) module</title><link href="https://www.qftest.com/en/blog/article/news-about-the-print-and-println-module.html" rel="alternate"/><published>2019-07-04T00:00:00+02:00</published><updated>2019-07-04T00:00:00+02:00</updated><author><name>Pascal Bihler</name></author><id>tag:www.qftest.com,2019-07-04:/en/blog/article/news-about-the-print-and-println-module.html</id><summary type="html">&lt;p&gt;If you are working with QF-Test scripting nodes, no matter whether you prefer Jython, Groovy or JavaScript, you will come across the QF module sooner or later. This module, accessible in all script nodes and script terminals via the variable qf, holds a bunch of helper methods designed to make the life of a script programmer easier.&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you are working with QF-Test scripting nodes, no matter whether you prefer Jython, Groovy or JavaScript, you will come across the QF module sooner or later. This module, accessible in all script nodes and script terminals via the variable qf, holds a bunch of helper methods designed to make the life of a script programmer easier.&lt;/p&gt;
&lt;p&gt;From time to time the module will be enhanced by new methods &amp;ndash; in QF-Test 4.6.0 by the methods  &lt;code&gt;print&lt;/code&gt; and &lt;code&gt;println&lt;/code&gt;. A good opportunity to have a look at the module and its new methods.&lt;/p&gt;
&lt;h2&gt;qf.print(ln)&lt;/h2&gt;
&lt;p&gt;The method &lt;code&gt;qf.print(…)&lt;/code&gt; prints a string (resp. the string value of an object) to the QF-Test terminal (and with SUT scripts also to the client terminal). &lt;code&gt;qf.println(…)&lt;/code&gt; adds a line break to the output to ensure the next output will be printed to a new line. But why not just use a simple  &lt;code&gt;print&lt;/code&gt; (&lt;code&gt;without qf&lt;/code&gt;. in front)?&lt;/p&gt;
&lt;p&gt;Theoretically yes, but&amp;#8230; The classic &lt;code&gt;print&lt;/code&gt; writes a string to the so-called &lt;em&gt;Standard Output&lt;/em&gt; of the program. QF-Test intercepts the string and redirects it to the terminal. Usually, this works all right, however, especially with SUT scripts, you might run into the problem that the client application itself redirects the &lt;em&gt;Standard Output&lt;/em&gt; thus preventing QF-Test ever to &amp;#8216;see&amp;#8217; the output. This can become really annoying when you need to resort to the classic &amp;#8216;print debugging&amp;#8217; when writing resolvers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qf.print(…)&lt;/code&gt; skirts the problem, not transmitting the string via the normal output channels, but sending it &amp;#8216;directly&amp;#8217; via the internal RMI interface (i.e. via the same &amp;#8216;line&amp;#8217; QF-Test uses to control the SUT and to send values for checks and events). Now, client specific adaptions will not matter anymore and on top it is slightly more efficient.&lt;/p&gt;
&lt;h3&gt;The small print&lt;/h3&gt;
&lt;p&gt;I would like to go into detail with some side effects:&lt;/p&gt;
&lt;p&gt;1. &lt;code&gt;qf.print(…)&lt;/code&gt; does not only take one parameter but as many as you like and joins them by space characters. This little JavaScript program:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="nx"&gt;qf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;will produce the terminal output &amp;#8216;&lt;code&gt;1 1 2'&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;2. &lt;code&gt;qf.print(…)&lt;/code&gt;calls the Java string method &lt;code&gt;toString()&lt;/code&gt; to generate the output. So, if you have been trying in vain to check whether the Jython &lt;code&gt;array&lt;/code&gt; module really produces a Java array:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;array&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# array(java.lang.String, [u&amp;#39;a&amp;#39;, u&amp;#39;b&amp;#39;, u&amp;#39;c&amp;#39;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;qf.println(…)&lt;/code&gt; will now be a solution:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;array&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="n"&gt;qf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# [Ljava.lang.String;@353191ee]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first script will print a Jython string representation of the array (&amp;#8220;When an array object is printed or converted to a string, it is represented as &lt;code&gt;array(typecode, initializer)&lt;/code&gt;. &amp;#8220;), however not representing the view of Java on the object. The second script will print the output of &lt;code&gt;toString()&lt;/code&gt; for Java arrays, i.e. the type information and its hash code (just for once this output will prove helpful in order to check it is a real Java array. In the Java world you would rather prefer &lt;code&gt;Arrays.deepToString(a)&lt;/code&gt; to see the content). If you want to print the Jython string representation just add  &lt;code&gt;str(…)&lt;/code&gt;: &lt;code&gt;qf.println(str(array.array(String,("a","b","c")))).&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3. Like the other &amp;#8216;standard&amp;#8217; variables (&lt;code&gt;rc&lt;/code&gt;, &lt;code&gt;out&lt;/code&gt;, &lt;code&gt;resolvers&lt;/code&gt;, &lt;code&gt;notifications&lt;/code&gt;, …) with Groovy scripts &lt;code&gt;qf&lt;/code&gt; principally only works on top level:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;InnerClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;printSomething&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Something&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;printSomethingElse&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;qf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SomethingElse&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InnerClass&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printSomething&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// =&amp;gt; Does not reach the terminal in a Server-Script nicht im Termial an, because &amp;quot;println&amp;quot; directly uses the standard output.  &lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printSomethingElse&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// =&amp;gt; &amp;quot;No such property: qf for class: InnerClass&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To get around this, you can transfer the bindings via the contructor to the inner class:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;InnerClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="n"&gt;qf&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;printSomething&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Something&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;printSomethingElse&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;qf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SomethingElse&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="kt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InnerClass&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;out:&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;qf:&lt;/span&gt;&lt;span class="n"&gt;qf&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printSomething&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printSomethingElse&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Simple and effective, especially when writing your own checker or when updating older resolvers.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Something doesn’t work in QF-Test</title><link href="https://www.qftest.com/en/blog/article/something-doesnt-work-with-qf-test.html" rel="alternate"/><published>2019-06-04T00:00:00+02:00</published><updated>2019-06-04T00:00:00+02:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2019-06-04:/en/blog/article/something-doesnt-work-with-qf-test.html</id><summary type="html">&lt;p&gt;You are working with QF-Test and something doesn&amp;#8217;t work as expected? Then you can (as usually) search the Internet or do research at StackOverflow&amp;#8230; A faster way is going on our channels &amp;#8230;&lt;/p&gt;</summary><content type="html">&lt;p&gt;You are working with QF-Test and something something doesn&amp;#8217;t work as expected? Then you can (as usually) search the Internet or do research at StackOverflow&amp;#8230; A faster way is going on our channels &amp;#8230;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/en/product/faq.html" title="FAQgeneral"&gt;General FAQ&lt;/a&gt;, &lt;a href="https://www.qftest.com/doc/manual/en/faq.html#app_faq" title="Technical FAQ"&gt;Technical FAQ&lt;/a&gt; and &lt;a href="/en/support/videos.html" title="QF-Test Videos"&gt;Videos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;context-sensitive help (click right mouse button on arbitrary tree node or attribute (in the details pane) → select &amp;#8220;What&amp;#8217;s this?&amp;#8221; → reference in the manual)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.qftest.com/doc/tutorial/en/contents.html"&gt;Tutorial&lt;/a&gt;, &lt;a href="https://www.qftest.com/doc/manual/en/contents.html"&gt;Manual&lt;/a&gt;, Mailing list, the Blog here&lt;/li&gt;
&lt;li&gt;full-text search (onsite search in the upper right corner) including manual, tutorial, standard lilbrary, QF-Test website, mailing list archive, blog&lt;/li&gt;
&lt;li&gt;All information for a successful start can be found on &lt;a href="/en/product/get-started-with-qf-test.html" title="Get started with QF-Test"&gt;Get started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Free starter webinar every Monday 4 p.m. CET/CEST&lt;/li&gt;
&lt;li&gt;Ask our support team directly:&lt;ul&gt;
&lt;li&gt;via mail to &lt;a href="mailto:support@qftest.com" title="Send email"&gt;support@qftest.com&lt;/a&gt; (please include your run log &amp;ndash; see manual Chapter 8.1 &amp;ndash; and test suites)&lt;/li&gt;
&lt;li&gt;via &lt;a href="https://services.qftest.com/en/support/request/"&gt;support form&lt;/a&gt; (in QF-Test&amp;#8217;s &amp;#8220;help&amp;#8221; menu &amp;#8220;Contact support team&amp;#8221;)&lt;/li&gt;
&lt;li&gt;phone our support hotline +49 8171 – 38648 20&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which help was especially valuable for you? We looking forward to your comments &amp;ndash; thanks.&lt;/p&gt;
&lt;p&gt;Slightly different topics to classic support are bug reports or feature requests. They are followed by individual blog posts. &lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>No coordinates based Events and no Image based checks! QF-Test is smarter than that! Part I</title><link href="https://www.qftest.com/en/blog/article/no-coordinates-based-events-and-no-image-based-checks-qf-test-is-smarter-than-that-part-i.html" rel="alternate"/><published>2019-05-29T00:00:00+02:00</published><updated>2019-05-29T00:00:00+02:00</updated><author><name>Plamen Vesselinov</name></author><id>tag:www.qftest.com,2019-05-29:/en/blog/article/no-coordinates-based-events-and-no-image-based-checks-qf-test-is-smarter-than-that-part-i.html</id><summary type="html">&lt;p&gt;Some days ago I had the pleasure to visit a Meetup Venue here in Leipzig, Germany. The topic was very interesting &amp;ndash; &amp;#8220;Experience with the introduction of Test Automation&amp;#8221;. Many of the participants consider Web UI testing very fragile and cumbersome to keep in &amp;#8220;Tests OK&amp;#8221; status that&amp;#8217;s why I I decided to write this series with the topic: No coordinates based Events and no Image based checks! QF-Test is smarter than that!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hello fellow QA Experts!&lt;/p&gt;
&lt;p&gt;Some days ago I had the pleasure to visit a Meetup Venue here in Leipzig, Germany. The topic was very interesting &amp;ndash; &amp;#8220;Experience with the introduction of Test Automation&amp;#8221;.&lt;/p&gt;
&lt;p&gt;I was (not) surprised that many of the participants consider Web UI testing very fragile and cumbersome to keep in &amp;#8220;Tests OK&amp;#8221; status. &lt;/p&gt;
&lt;p&gt;Especially in the early stages of the web development when Tests are already created the unexpected changes in the Web Page structure, Components&amp;#8217; IDs or Names can lead to overwhelming tests tweaking. Furthermore lots people think that because of this limitation of automated web tests only Image Based Checks and Absolute Mouse-Events are used by testing-tool.&lt;/p&gt;
&lt;p&gt;For those that do not know QF-Test very well and for those that have not evaluated QF-Test I have decided to write a few post Blog series with the topic:&lt;/p&gt;
&lt;p&gt;No coordinates based Events and no Image based checks! QF-Test is smarter then that! &lt;/p&gt;
&lt;p&gt;&lt;img alt="Smart web testing with QF-Test" src="/blog/resources/qf-test-smart-web-testing.png" title="Smart web testing with QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;P.S. Many thanks for the great place and hosting of the Meetup at Saxonia Systems AG, Leipzig.&lt;/p&gt;
&lt;p&gt;Stay tuned,&lt;/p&gt;
&lt;p&gt;Plamen Vesselinov&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Mark nodes via colors</title><link href="https://www.qftest.com/en/blog/article/mark-nodes-via-colors.html" rel="alternate"/><published>2019-05-14T00:00:00+02:00</published><updated>2019-05-14T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-05-14:/en/blog/article/mark-nodes-via-colors.html</id><summary type="html">&lt;p&gt;In order to simplify test suite editing it is possible to assign colored marks to nodes. This feature is especially (but not only) helpful in the context of complex find and replace operations &amp;ndash; in this case, the color markers can for example be used to indicate that a node still needs revision or that a node was edited successfully.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In order to simplify test suite editing it is possible to assign colored marks to nodes. This feature is especially (but not only) helpful in the context of complex find and replace operations &amp;ndash; in this case, the color markers can for example be used to indicate that a node still needs revision or that a node was edited successfully. It may even make sense to use another color mark in order to indicate that you may want to re-look at a certain edited node later on.&lt;/p&gt;
&lt;p&gt;There are several ways to assign color markers to nodes. Manually this is often done via the Edit → Marks menu. In this menu first activate the desired color mark. Then via Edit → Marks → Toggle mark it is possible to toggle the desired color mark of the currently selected node. The following image shows different mouse-click nodes. While the first mouse-click node does not have any color marker, the second click has a blue marker, the third a red marker, and so on:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Colored Nodes in QF-Test" src="/blog/resources/coloredNodes.png" title="Colored Nodes in QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;As can be seen in above picture, QF-Test is also providing the parent-nodes with corresponding smaller color marks. Like this users can quickly see whether any node in the tree (still) has any color marker and in which subtree the node with the corresponding color mark can be found.&lt;/p&gt;
&lt;p&gt;Via the search dialog (Edit → Search) it is also possible to assign color marks to nodes. To do this, select the button &amp;#8220;Show result list&amp;#8221; in the search dialog. The dialog that opens lists all nodes that fulfill the previously entered search criterion. With the keyboard shortcut &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;A&lt;/kbd&gt; it is now possible to select all nodes. You can then assign the selected color to the selected node:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Search by Color in QF-Test" src="/blog/resources/colorBySearch.png" title="Search by Color in QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;A search based on these colors is possible. Just click on the yellow &amp;gt;&amp;gt; symbol in the search dialog to reach the extended search. Then you can choose to search only nodes having a certain color mark.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Search for colored nodes" src="/blog/resources/search.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Remarks&lt;/h2&gt;
&lt;p&gt;Please note that QF-Test is not saving color marks in .qft files. This means that after saving, closing and re-opening a test suite, all previously set color marks disappear. In order to save a color mark, you need to enter @green, @red, @blue or @yellow in the comment field of a node. When QF-Test opens a test suite, it will interpret these doctags and set the appropriate markers.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Mouse Events Explained – Web</title><link href="https://www.qftest.com/en/blog/article/mouse-events-explained-web.html" rel="alternate"/><published>2019-05-06T00:00:00+02:00</published><updated>2019-05-06T00:00:00+02:00</updated><author><name>Plamen Vesselinov</name></author><id>tag:www.qftest.com,2019-05-06:/en/blog/article/mouse-events-explained-web.html</id><summary type="html">&lt;p&gt;QF-Test uses semi-hard events as default (based on web framework, browser and browser version, OS etc), but not all cases. There are three event types available.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here comes one more short blog article, which will shed some light on the mouse events and specifically semi-hard events. QF-Test uses semi-hard events as default (based on web framework, browser and browser version, OS etc), but not all cases.&lt;/p&gt;
&lt;p&gt;There are three event types available&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Soft:&lt;/strong&gt; A soft event is an artificial event which is inserted directly into the event queue. They are inserted directly at DOM/JavaScript level.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semi hard:&lt;/strong&gt; QF-Test own terminology. This is an artificial system event which gets sent to the browser. The browser has to interpret it and create the relative (soft) events for the web application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hard:&lt;/strong&gt; a real movement of the mouse cursor and a simulation of a mouse down and mouse up which triggers a system/os event.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Set &lt;em&gt;semi hard&lt;/em&gt; events via SUT Script before the mouse click:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rc.setOption(Options.OPT_WEB_SEMI_HARD_EVENTS, True)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;reset the option via:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rc.unsetOption(Options.OPT_WEB_SEMI_HARD_EVENTS)&lt;/code&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Disabled node</title><link href="https://www.qftest.com/en/blog/article/disabled-node.html" rel="alternate"/><published>2019-05-03T00:00:00+02:00</published><updated>2019-05-03T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-05-03:/en/blog/article/disabled-node.html</id><summary type="html">&lt;p&gt;In some cases it is helpful to disable nodes temporarily. Here is a short article how to do this.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In some cases it is helpful to disable nodes temporarily. Disabled nodes are skipped during test execution. To deactivate or reactivate a node, either the keyboard shortcut &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;D&lt;/kbd&gt; or the menu item Edit → Toggle disabled state can be used.&lt;/p&gt;
&lt;p&gt;In the tree, deactivated nodes are displayed in gray so that it is easy to distinguish between active and inactive nodes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Enabled and disabled node" src="/blog/resources/acive-inactive-node.png" title="Enabled and disabled node" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>“license”, “license.new”, “license.txt”, “license.dat”, “license.old”, “license.server” … Who’s gonna know about that?</title><link href="https://www.qftest.com/en/blog/article/license-licensenew-licensetxt-licensedat-licenseold-licenseserver-whos-gonn.html" rel="alternate"/><published>2019-04-24T00:00:00+02:00</published><updated>2019-04-24T00:00:00+02:00</updated><author><name>Mike Schmidt</name></author><id>tag:www.qftest.com,2019-04-24:/en/blog/article/license-licensenew-licensetxt-licensedat-licenseold-licenseserver-whos-gonn.html</id><summary type="html">&lt;p&gt;Indeed, it is quite confusing if you would take a look how the supposed license files attached to emails are arriving. Although it is actually quite simple …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Indeed, it is quite confusing if you would take a look how the supposed license files attached to emails are arriving. Although it is actually quite simple …&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test different license suffixes" src="/blog/resources/license-collage.png" title="QF-Test different license suffixes" /&gt;&lt;/p&gt;
&lt;h2&gt;&amp;#8220;license&amp;#8221;&lt;/h2&gt;
&lt;p&gt;The name of a full QF-Test license file is &amp;#8220;license&amp;#8221;. As QF-Test was developed under Linux originally there was no apparently reason to add an extension to the filename.&lt;br /&gt;
 Why we should? Linux doesn’t interpret the extensions.&lt;/p&gt;
&lt;p&gt;Looking at Windows we have a totally different behavior. Windows is using the fie extensions to determine the type of the file (Linux is cleverer in that case). For that reason, each administrator in a Windows environment is configuring the computer in the company that the file explorer will &lt;strong&gt;always&lt;/strong&gt; show the file extensions (so that you don’t open attachments like &amp;#8220;order.pdf&amp;#8221; and then it turns out that the attachment was named &amp;#8220;order.pdf.exe&amp;#8221; instead and was a malware).&lt;br /&gt;
 But I’m digressing…&lt;/p&gt;
&lt;p&gt;So under Windows files without an extension are not allowed at all (in fact they are possible despite of that).&lt;br /&gt;
 And there is now the e-mail client, which in its emergency with a file &amp;#8220;license&amp;#8221; seals in its opinion a suitable extension. Outlook likes to attach a neutral &amp;#8220;.dat&amp;#8221;. Thunderbird looks into the content and finds that it is an ASCII text file, so it appends a &amp;#8220;.txt&amp;#8221; to the file. Other email clients can make other decisions here, but will never attach a &amp;#8220;.new&amp;#8221; or &amp;#8220;.server&amp;#8221;.&lt;/p&gt;
&lt;p&gt;So you only need to know that all QF-Test license files attached, which are not named &amp;#8220;license.new&amp;#8221; or &amp;#8220;license.server&amp;#8221;, are full QF-Test license files which should be saved as &amp;#8220;license&amp;#8221;.&lt;/p&gt;
&lt;h2&gt;&amp;#8220;license.new&amp;#8221;. What’s about that?&lt;/h2&gt;
&lt;p&gt;This is a license update or license upgrade file. Such files need a fitting full license file to create a file &amp;#8220;license&amp;#8221; locally.&lt;br /&gt;
 The old file will be renamed to &amp;#8220;license.old&amp;#8221; by the way and may be deleted. If you wouldn’t do this, next time a file &amp;#8220;license.old2&amp;#8221; will appear and so on.&lt;/p&gt;
&lt;p&gt;Thus this file never can be used for QF-Test at a new installed machine. Instead the, ideally, once created file &amp;#8220;license&amp;#8221; must be used.&lt;/p&gt;
&lt;p&gt;Therefore you should provide the resultant file &amp;#8220;license&amp;#8221; after an update or upgrade process always to your affected colleagues.&lt;/p&gt;
&lt;h2&gt;Special case &amp;#8220;license.server&amp;#8221;&lt;/h2&gt;
&lt;p&gt;This file is in this sense a special case as it is a full license file, but only for the QF-Test license server (in case you would use it instead of the standard license mechanism). But the QF-Test installations which are communicating with that license server are using a file &amp;#8220;license&amp;#8221;, too. The file &amp;#8220;license&amp;#8221; contains the information how to address the license server in that case.&lt;/p&gt;
&lt;h2&gt;My license file still seems not to be valid&lt;/h2&gt;
&lt;p&gt;In such rare cases that your license file still seems no to be valid although you have checked to possible reasons explained above we will help you, of course. In such a case &lt;a href="https://services.qftest.com/en/support/contact/?type=license"&gt;please let us know&lt;/a&gt; what you already have tried and what happened in detail, please.&lt;/p&gt;</content><category term="blog"/><category term="license"/></entry><entry><title>Writing your own checkers</title><link href="https://www.qftest.com/en/blog/article/writing-your-own-checkers.html" rel="alternate"/><published>2019-04-16T00:00:00+02:00</published><updated>2019-04-16T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-04-16:/en/blog/article/writing-your-own-checkers.html</id><summary type="html">&lt;p&gt;While testing an application, one of the most important steps is to verify that the actual state of the application is correct.&lt;/p&gt;</summary><content type="html">&lt;p&gt;While testing an application, one of the most important step is to verify that the actual state of the application is correct. For example, when we test a calculator application, then at a certain moment we need to verify that the application has calculated the right result. This means, we need some kind of action (called check) to verify that some kind of UI component, e.g. a textfield is showing the right piece of information. &lt;/p&gt;
&lt;p&gt;The most easiest way to record a check action is to enter the check recording mode, e.g. by clicking on the corresponding item in the toolbar.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test toolbar" src="/blog/resources/recordCheck.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Record Check 2" src="/blog/resources/recordCheck2.png" /&gt;&lt;/p&gt;
&lt;p&gt;Then right click the corresponding component and select the wanted check.&lt;/p&gt;
&lt;p&gt;Wait a minute, what should we do, if the wanted check is not in that list? Well, either we use a SUT script in order to verify the component state or we simply &lt;a href="https://www.qftest.com/doc/manual/en/tech_checkers.html#sec_checkers" title="Open external link in new window"&gt;implement our own check&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Please note, both ways to verify the component state are similar and both require programming knowledge and &amp;ndash; most often &amp;ndash; some knowledge about the used UI technology and/or how the component itself was implemented. Thus it may be a good idea to ask one of the developers for help.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s give an example. As shown in the above image, the list of possible checks does include a &amp;#8220;text color&amp;#8221; check. I will use Jython here, but you can do the same thing with Groovy or Java Script (or any other scripting language that may be added in future).&lt;/p&gt;
&lt;h2&gt;1. Analyze the UI component&lt;/h2&gt;
&lt;p&gt;When we don&amp;#8217;t know, how exactly the text color is implemented, we need to analyze the properties / methods of the component in order to find a method that returns the wanted information. The easiest way to do so in Jython is the &lt;a href="https://docs.python.org/2/library/functions.html#dir" title="Open external link in new window"&gt;dir&lt;/a&gt; and the &lt;a href="https://docs.python.org/2/library/functions.html#type" title="Open external link in new window"&gt;type&lt;/a&gt; command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;com = rc.getComponent("&amp;lt;QF-Test component id&amp;gt;")  
 print type(com)  
 print dir(com)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can get a QF-Test component id for your component by simply recording a click or any other action on the desired component. Instead of the print statements you can also use the &lt;code&gt;rc.logMessage()&lt;/code&gt; method. Then the output will be written into the run log.&lt;/p&gt;
&lt;p&gt;In my example&lt;/p&gt;
&lt;p&gt;&lt;code&gt;type(com)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;will return that the component is a standard javax.swing.JTextField. So we can have a look at the corresponding &lt;a href="https://docs.oracle.com/javase/8/docs/api/javax/swing/JTextField.html"&gt;documentation&lt;/a&gt; to find out that it is possible to implement such a thing via the setForeground() method. This would mean that we can get the corresponding data via the getForeground() method:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;com = rc.getComponent("&amp;lt;QF-Test component id&amp;gt;")  
 print com.getForeground().getRGB()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;will return the desired color as integer and bingo, this will print the color as integer. &lt;/p&gt;
&lt;h2&gt;2. Step, add a SUT script&lt;/h2&gt;
&lt;p&gt;Now let&amp;#8217;s use our knowledge that the getForeground() method will return the desired color as integer and simply wrap this code into a checker script: &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.extensions.checks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CheckerRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Checker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DefaultCheckType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CheckDataType&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.apps.qftest.shared.data.check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StringCheckData&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;de.qfs.lib.util&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pair&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jarray&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="n"&gt;componentClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;javax.swing.JTextField&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;foregroundColorCheckerType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DefaultCheckType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CheckDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Foreground color&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ForegroundColorChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Checker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getSupportedCheckTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jarray&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;foregroundColorCheckerType&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DefaultCheckType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getCheckData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkType&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foregroundColorCheckerType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getIdentifier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getIdentifier&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getForeground&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getRGB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StringCheckData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getIdentifier&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&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;color&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getCheckDataAndItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkType&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getCheckData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="k"&gt;global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foregroundColorChecker&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="c1"&gt;# unregister a maybe already present instance of this checker  &lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CheckerRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unregisterChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;componentClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foregroundColorChecker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="c1"&gt;# after unregistering, register a new instance of this checker  &lt;/span&gt;
&lt;span class="n"&gt;foregroundColorChecker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForegroundColorChecker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="n"&gt;CheckerRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registerChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;componentClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foregroundColorChecker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="QF-Test Record Check" src="/blog/resources/recordCheck3.png" /&gt;&lt;/p&gt;
&lt;p&gt;Now after we have executed this script, the corresponding checker will be available in the checker list  and the corresponding checker can get recorded and replayed.&lt;/p&gt;
&lt;h3&gt;Remarks&lt;/h3&gt;
&lt;p&gt;This is a basic example and in real life you may want to convert the integer representation of the color into a more readable color representation.&lt;/p&gt;
&lt;p&gt;You may use your own checker to implement checks for custom components.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>If you want to work in Germany, come to us</title><link href="https://www.qftest.com/en/blog/article/if-you-want-to-work-in-germany-come-to-us.html" rel="alternate"/><published>2019-04-09T00:00:00+02:00</published><updated>2019-04-09T00:00:00+02:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2019-04-09:/en/blog/article/if-you-want-to-work-in-germany-come-to-us.html</id><summary type="html">&lt;p&gt;QFS (Quality First Software GmbH) is one of the best employers of the ITC industry in Germany and also in Bavaria. We are located in Leipzig and in Geretsried (that’s between Munich and the Alps at the Isar river) and have won the Great Place to Work award already two times now.&lt;/p&gt;</summary><content type="html">&lt;p&gt;QFS (Quality First Software GmbH) is one of the &lt;a href="/en/company/jobs/great-place-to-work.html" title="QFS is a Great Place to Work"&gt;best employers of the ITC industry in Germany and also in Bavaria&lt;/a&gt;. We are placed in Geretsried and Leipzig and have won the Great Place to Work award already two times now. Geretsried is between Munich and the Alps at the Isar river. &lt;/p&gt;
&lt;p&gt;In 2018 we took first place as newcomer in the category of firms with less than 50 employees. This result was presented in the Great Place to Work Institute in Munich. This year we won as well in this voluntary audit. The anonymous employees’ opinions count twice in comparison to the company’s report about fairness, credibility, respect, pride and team spirit. All of our 16 employees took part in the survey.&lt;/p&gt;
&lt;p&gt;Here is a short overview of what QFS offers: Besides two home office days for the whole company, we go and have lunch together paid by the company three times per month. Who wants to have a short nap afterwards can sleep whenever there is the need except in the afternoon when we are playing table football.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Great Place To Work ITK 2019 Martina Schmid, Plamen Vesselinov" src="/blog/resources/2019-great-place-to-work-itk-ts-pv.jpg" /&gt;&lt;/p&gt;</content><category term="blog"/><category term="company"/></entry><entry><title>Accessing sub-items of UI components</title><link href="https://www.qftest.com/en/blog/article/accessing-sub-items-of-gui-components.html" rel="alternate"/><published>2019-04-01T00:00:00+02:00</published><updated>2019-04-01T00:00:00+02:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-04-01:/en/blog/article/accessing-sub-items-of-gui-components.html</id><summary type="html">&lt;p&gt;In most cases we are dealing with more or less straight-forward UI components,such as buttons and text fields. Nevertheless we often have to deal with morecomplex UI components like trees, tables and lists too. These UI components have sub-items.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In most cases we are dealing with more or less straight-forward UI components, such as buttons and text fields. Nevertheless we often have to deal with more complex UI components like trees, tables and lists too. These UI components have sub-items. For example the UI component &amp;#8220;Table&amp;#8221; consists out of different table cells (=the sub-items of the table):&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Table CarConfig" src="/blog/resources/table.png" /&gt;&lt;/p&gt;
&lt;p&gt;QF-Test provides multiple ways to access sub-items of UI components:&lt;/p&gt;
&lt;h2&gt;Using the &amp;#8220;QF-Test component ID&amp;#8221; to specify the wanted sub-item&lt;/h2&gt;
&lt;p&gt;In the &amp;#8220;QF-Test component ID&amp;#8221; attribute it is possible to specify that an action should be replayed on the sub-item of a UI component. In order to do so an identifier is added to the component id. This identifier may either be numerical (represented via the character &amp;amp;), textual (represented via the character @) or the identifier may be a regular expression (represented via the character %).&lt;/p&gt;
&lt;h2&gt;Examples&lt;/h2&gt;
&lt;h3&gt;List or comboboxes&lt;/h3&gt;
&lt;p&gt;You only need one identifier to describe a sub-item in a list or a combobox:&lt;/p&gt;
&lt;p&gt;&lt;img alt="ComboBox" src="/blog/resources/comboBox.png" title="ComboBox" /&gt;&lt;/p&gt;
&lt;p&gt;As can be seen, the above combobox has 4 different sub-items. The sub-item &amp;#8220;- no special model -&amp;#8221; (with the numerical index 0), &amp;#8220;Gomera&amp;#8221; (with the numerical index 1), &amp;#8220;Jazz&amp;#8221; (with the numerical index 2) and the sub-item &amp;#8220;Luxus&amp;#8221; (with the numerical index 3). So either &lt;code&gt;qftestIdOfComboBox@Jazz&lt;/code&gt; or &lt;code&gt;qftestIdOfComboBox&amp;amp;2&lt;/code&gt; will access the sub-item &lt;code&gt;Jazz&lt;/code&gt; in this combobox. Alternatively we can also use &lt;code&gt;qftestIdOfComboBox%J.*&lt;/code&gt; &amp;ndash; as &lt;code&gt;J.*&lt;/code&gt; is a regular expression that is matching the sub-item &amp;#8220;Jazz&amp;#8221;.&lt;/p&gt;
&lt;p&gt;And in order to access the sub-item &amp;#8220;Gomera&amp;#8221; we can use:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Example for a subitem" src="/blog/resources/exampleSubItem.png" title="Example for a subitem" /&gt;&lt;/p&gt;
&lt;h3&gt;Tables&lt;/h3&gt;
&lt;p&gt;In the case of a table two identifiers are needed. The first identifier is specifying the column and the second identifier the row of the wanted cell. So if we have the following table:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Table CarConfig" src="/blog/resources/table.png" /&gt;&lt;/p&gt;
&lt;p&gt;An action on the QF-Test component ID &lt;code&gt;qftestIdOfTable@Price%.*12.*&lt;/code&gt; will target the cell that contains the text &lt;code&gt;$12,300.00&lt;/code&gt;. The same cell gets targeted by the QF-Test component ID &lt;code&gt;qftestIdOfTable@Price&amp;amp;0&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Trees&lt;/h3&gt;
&lt;p&gt;In order to access sub-items in a tree, we need an identifier that is taking into account the hierarchical framework of this UI component. So if we have the following tree:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Tree" src="/blog/resources/tree.png" /&gt;&lt;/p&gt;
&lt;p&gt;The sub-item &amp;#8220;Mats&amp;#8221; can be accessed via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;qftestIdOfTree@/Information/Accessories/Mats&lt;/li&gt;
&lt;li&gt;qftestIdOfTree&amp;amp;/0/0/2&lt;/li&gt;
&lt;li&gt;qftestIdOfTree%/Inf.*/Acc.*/M.*&lt;/li&gt;
&lt;li&gt;qftestIdOfTree%/.*/.*/Mats&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(and via other regular expressions). However it will not work to access the sub-item Mats via the QF-Test component ID &lt;code&gt;qftestIdOfTree%/.*/Mats&lt;/code&gt;. This is because with &lt;code&gt;%/.*/Mats&lt;/code&gt; we are advising QF-Test to search a sub-item matching the regular expression &lt;code&gt;.*&lt;/code&gt; on the first level of the tree. In this example there is only one first level sub-item (&amp;#8220;Information&amp;#8221;). The regular expression provided to QF-Test (&lt;code&gt;.*&lt;/code&gt;) is matching this sub-item. QF-Test will then go on and use the next regular expression (&amp;#8220;Mats&amp;#8221;) in order to search through the child nodes of any matching node (&amp;#8220;Accessories&amp;#8221;, &amp;#8220;Description&amp;#8221;). As can be seen, both strings are not matching the desired regular expression, so &lt;code&gt;%/.*/Mats&lt;/code&gt; does not work.&lt;/p&gt;
&lt;h2&gt;Using item nodes to specify the wanted sub-item&lt;/h2&gt;
&lt;p&gt;Item nodes provide the same functionality, namely to specify the wanted sub-item by index, name or regular expression. However these item nodes are used less commonly:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Example Item Node" src="/blog/resources/exampleItemNode.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can insert these nodes over the menubar (Insert → Component nodes → Item) as child nodes of any list, table or tree component node (or more general every node representing a component that supports sub-items):&lt;/p&gt;
&lt;p&gt;&lt;img alt="QF-Test Example Item node in tree" src="/blog/resources/exampleItemNodeInTree.png" /&gt;&lt;/p&gt;
&lt;p&gt;The text specified in the &amp;#8220;QF-Test ID&amp;#8221; attribute of this node (GomeraItemInComboBox in the given example, but you can use nearly any other text as well) can then be used in the &amp;#8220;QF-Test component ID&amp;#8221; attribute of a mouse event node (or any other event replaying node). When such an event node gets executed, QF-Test will first determine the wanted UI component via the component recognition information provided by the parent node of the inserted item node. Then QF-Test will determine the wanted sub-item by the information provided in the inserted item node. Once this is done, the desired action will be replayed on that sub-item.&lt;/p&gt;
&lt;h2&gt;QPath&lt;/h2&gt;
&lt;p&gt;Sometimes we need to access sub-components that are located within sub-items of any UI component. This may (for example) be a button or a checkbox located inside a table cell. In order to access these subcomponents, we can use the &lt;code&gt;@:ClassName&lt;/code&gt;-syntax (the so called &lt;a href="https://www.qftest.com/doc/manual/en/user_subitems.html#usec_multilevelsubitemsqpath" title="QPath"&gt;QPath&lt;/a&gt; syntax). For example, if we have a button located inside a table cell, we can access it via: &lt;code&gt;qfcomponentIdOfTheTable@columnname@rowname@:Button&amp;lt;0&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;XPath and CSS Selectors&lt;/h2&gt;
&lt;p&gt;When you are testing a web-application it is also possible to use an XPath or CSS Selector&lt;/p&gt;
&lt;p&gt;in order to access the wanted UI component and/or sub-item. As &lt;a href="https://www.qftest.com/doc/manual/en/user_subitems.html#usec_xpath" title="QPath and CSS selectors"&gt;XPath and CSS selectors&lt;/a&gt; have been proven to be relatively difficult to write they are normally not my first choice. However this feature is nevertheless quite useful when it comes to rewriting already existing Selenium web tests into QF-Test.&lt;/p&gt;
&lt;p&gt;The general syntax in order to use an XPath/CSS selector inside a &amp;#8220;QF-Test component ID&amp;#8221; attribute of an event node is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;genericHtml@:xpath=${quoteitem:yourXPathGoesHere}&lt;/li&gt;
&lt;li&gt;genericHtml@:css=${quoteitem:yourCSSSelectorGoesHere}&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternatively it is possible to specify an xpath and/or css selector in the extra features of a component node (or in SUT scripts). This can be done via a &amp;#8220;Must match&amp;#8221; extra feature with the name &lt;code&gt;qfs:item&lt;/code&gt; and the value &lt;code&gt;@:xpath=${quoteitem:yourXPathGoesHere}&lt;/code&gt;. (In case you want to use a css selector use &lt;code&gt;@:css=${quoteitem:yourCSSSelectorGoesHere}&lt;/code&gt; as value.)&lt;/p&gt;
&lt;h3&gt;Notes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Out of obvious reasons the @:xpath/@:css-syntax assumes that the used XPath/CSS-Syntax returns a single component. The usage of a XPath-syntax that does not return a single component, but for example an integer (Example: &lt;code&gt;count(.//input[@id!='Google'])&lt;/code&gt;) or a boolean (Example: &lt;code&gt;nilled($in-xml//child[1])&lt;/code&gt;), may lead to unexpected behavior.&lt;/li&gt;
&lt;li&gt;If you know what you are doing, you can nest multiple xpath/css selector statements. For example: &lt;code&gt;genericHtml@:xpath=${quoteitem:firstXPath}@:xpath=${quoteitem:secondXPath}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For those of you who are wondering what the &lt;code&gt;genericHtml&lt;/code&gt; part in the string &lt;code&gt;genericHtml@:xpath=${quoteitem:yourXPathGoesHere}&lt;/code&gt; means. This substring is referring to the &lt;code&gt;genericHtml&lt;/code&gt; component node that is specified in the &lt;a href="https://www.qftest.com/doc/manual/en/user_stdlib.html#usec_stdlib" title="Standard library"&gt;qfs.qft&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The used XPath is searching for the wanted component from this UI component onwards.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Examples&lt;/h3&gt;
&lt;p&gt;Let&amp;#8217;s assume you have a single text input field on your website that is implemented via:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="na"&gt;additional&lt;/span&gt; &lt;span class="na"&gt;attributes&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;XPath within a QF-Test component ID&lt;/h4&gt;
&lt;p&gt;In event nodes it is possible to address the text input field (in the above sample website) via the QF-Test component ID &lt;code&gt;genericHtml@:xpath=${quoteitem:.//input}&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;CSS Selector within a QF-Test component ID&lt;/h4&gt;
&lt;p&gt;In event nodes it is possible to address the text input field (in the above sample website) via the QF-Test component ID &lt;code&gt;genericHtml@:css=${quoteitem:input}&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;XPath within a QF-Test component node&lt;/h4&gt;
&lt;p&gt;The following image show an example component node that is specifying an XPath in its extra features:&lt;/p&gt;
&lt;p&gt;&lt;img alt="xPath in ComponentNodes QF-Test" src="/blog/resources/xpathInComponentNodes.png" /&gt;&lt;/p&gt;
&lt;p&gt;Once this component node got inserted as a sub-children of the &amp;#8220;Windows and components&amp;#8221; node, it is then possible to use the string &amp;#8220;myXPathEle&amp;#8221; (or whatever got specified in the &amp;#8220;QF-Test ID&amp;#8221; of this component node) in the &amp;#8220;QF-Test component ID&amp;#8221; attribute of event nodes.&lt;/p&gt;
&lt;h4&gt;XPath and/or CSS Selectors within SUT Scripts&lt;/h4&gt;
&lt;p&gt;In order to use an xpath in a SUT script, the following statements can be used:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;com = rc.getComponent(&amp;quot;genericHtml&amp;quot;) # or rc.getComponent(&amp;quot;genericDocument&amp;quot;)  
res = com.getByXPath(rc.lookup(&amp;quot;xpath&amp;quot;))    # find subcomponent via xpath  
res = com.getByCSS(rc.lookup(&amp;quot;css&amp;quot;))        # find subcomponent via css  
res = com.getAllByXPath(rc.lookup(&amp;quot;xpath&amp;quot;)) # find all subcomponent via xpath  
res = com.getAllByCSS(rc.lookup(&amp;quot;css&amp;quot;))     # find all subcomponent via css
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Further reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.qftest.com/doc/manual/en/user_subitems.html" title="Subitems"&gt;Subitems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.qftest.com/doc/manual/en/user_components.html" title="Components"&gt;Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Password encryption</title><link href="https://www.qftest.com/en/blog/article/password-encryption.html" rel="alternate"/><published>2019-03-05T00:00:00+01:00</published><updated>2019-03-05T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-03-05:/en/blog/article/password-encryption.html</id><summary type="html">&lt;p&gt;Sensitive information like passwords should be treated with care. However often tests need to fill out a login form and thus these tests require to know the password.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sensitive information like passwords should be treated with care. However often tests need to fill out a login form and thus these tests require to know the password.&lt;/p&gt;
&lt;p&gt;In order to keep passwords save, it is possible to tell QF-Test to encrypt them. This may be done by finding the &amp;#8220;Text input&amp;#8221; node that is inputting the password into the password field. In this node right-click the text attribute, then chose &amp;#8220;Crypt password&amp;#8221;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Encrypt Password QF-Test" src="/blog/resources/encryptPassword_en.png" title="Encrypt Password in QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;While executing this input text step, QF-Test will then decrypt the encrypted password in order to input the password in the corresponding login password field. The decrypted password will not be mentioned in the &lt;a href="https://www.qftest.com/doc/manual/en/user_debugging.html#usec_runlog"&gt;run log&lt;/a&gt; or other reports created by QF-Test.&lt;/p&gt;
&lt;h3&gt;Remarks&lt;/h3&gt;
&lt;p&gt;It is a good idea to set a &lt;a href="https://www.qftest.com/doc/manual/en/opt_play.html#sec_opt_play"&gt;salt&lt;/a&gt; in order to improve the password encryption. In order to do so open the options dialog (menu: Edit → Options&amp;#8230;). In this dialog open the replay options and set a random string as password encryption salt:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Salt Password encryption" src="/blog/resources/setPasswordEncryptionSalt_en.png" title="Salt for Password encryption in QF-Test" /&gt;&lt;/p&gt;
&lt;p&gt;As the salt plays an important role in the password en-/decrypting algorithm, the salt has to be set before the password gets encrypted.&lt;/p&gt;
&lt;p&gt;The salt is saved in the &lt;a href="https://www.qftest.com/doc/manual/en/user_installation.html#usec_configfile"&gt;system.cfg&lt;/a&gt;. So by ensuring that all QF-Test instances in your network are using the same system.cfg configuration file, you can ensure that all QF-Test instances can encrypt/decrypt the password. The &lt;a href="https://www.qftest.com/doc/manual/en/tech_execution.html#sec_execution"&gt;&amp;#8221;-systemcfg &amp;#8221; commandline argument&lt;/a&gt; may be used in order to ensure this.&lt;/p&gt;
&lt;p&gt;Pay attention: Everyone who has both, the password salt and the encrypted password has the possibly to decrypt your password.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Creating custom HTML/XML/Junit reports</title><link href="https://www.qftest.com/en/blog/article/creating-custom-htmlxmljunit-reports.html" rel="alternate"/><published>2019-02-28T00:00:00+01:00</published><updated>2019-02-28T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-02-28:/en/blog/article/creating-custom-htmlxmljunit-reports.html</id><summary type="html">&lt;p&gt;Via the run log QF-Test provides a detailed log about the actions it executed during a test run. The overall results of this run log can be summarized in other reports like the HTML/XML/Junit report.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Via the &lt;a href="https://www.qftest.com/doc/manual/en/user_debugging.html#usec_debugging" title="Run-log"&gt;run log&lt;/a&gt; QF-Test provides a detailed log about the actions it executed during a test run. The overall results of this run log can be summarized in other reports like the &lt;a href="https://www.qftest.com/doc/manual/en/user_report.html#usec_report" title="Reports"&gt;HTML/XML/Junit report&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A not well documented &amp;ndash; but gladly used &amp;ndash; feature is that it is possible to adapt/change the style/content of these reports to the own needs. Nevertheless, in order to do so, basic programming knowledges are inevitably needed.&lt;/p&gt;
&lt;p&gt;The HTML/XML/Junit reports are customizable. So the content of the &amp;#8220;name&amp;#8221; and &amp;#8220;package&amp;#8221; attribute can get changed.&lt;/p&gt;
&lt;p&gt;The HTML/XML/JUnit reports are generated via an &lt;a href="https://en.wikipedia.org/wiki/XSLT"&gt;XSLT-Transformation&lt;/a&gt;. The therefore needed XSLT sheets are located in the directory &lt;code&gt;&amp;lt;Path to the QF-Test installation directory&amp;gt;\qftest\qftest-$(version)\report&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you are changing these Sheets, you may want to copy/move them to &lt;code&gt;&amp;lt;Path to the QF-Test installation directory&amp;gt;\qftest\report&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As the XSL transformation sheets in this directory are preferred over the XSLT sheets in the &lt;code&gt;&amp;lt;Path to the QF-Test installation directory&amp;gt;\qftest\qftest-$(version)\report&lt;/code&gt; directory. Thus, your modified XSLT sheets will then also be used by later QF-Test versions. However &amp;ndash; as an disadvantage &amp;ndash; whenever a new QF-Test version is coming out with any XSLT-Sheets improvements, you will not notice these improvements anymore (as QF-Test will keep using the Sheets in the &lt;code&gt;&amp;lt;Path to the QF-Test installation directory&amp;gt;\qftest\report&lt;/code&gt; directory).&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;p&gt;The XSLT Identity Transformation may be used in order to get the original XML document out of which the HTML/XML/JUnit-Reports are getting generated:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;xsl:stylesheet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;xmlns:xsl=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.w3.org/1999/XSL/Transform&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;xsl:template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;match=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@*|node()&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;xsl:copy&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;xsl:apply-templates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;select=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;@*|node()&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/xsl:copy&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/xsl:template&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/xsl:stylesheet&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When generating an HTML report, you may want that QF-Test copies additional files (like additional JavaScript or stylesheet files) into the generated target directory. It is possible to inform QF-Test about such files. Therefore one simply creates a file with the name &amp;#8216;files-to-copy.txt&amp;#8217;. In this text file the additional JavaScript, stylesheet or image files are specified. (One file by line.)&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>How to create procedure calls</title><link href="https://www.qftest.com/en/blog/article/how-to-create-procedure-calls.html" rel="alternate"/><published>2019-02-22T00:00:00+01:00</published><updated>2019-02-22T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-02-22:/en/blog/article/how-to-create-procedure-calls.html</id><summary type="html">&lt;p&gt;Procedures (also called function or subroutine) may be used in order to solve often (re-)occurring challenges.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Subroutine" title="Wikipedia article Subroutine/Procedures"&gt;Procedures&lt;/a&gt; (also called function or subroutine) may be used in order to solve often (re-)occurring challenges. Like this, we may specify that certain successive actions are needed in order to solve a challenge (=programming a procedure). When we then encounter this challenge, we can simply call the procedure (and let the procedure solve this challenge for us). &lt;/p&gt;
&lt;h2&gt;How to insert a procedure call within QF-Test&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Go to the point in your test suite where you want to call a certain procedure.&lt;/li&gt;
&lt;li&gt;Insert a procedure call. In order to do so you may use the shortcut &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;A&lt;/kbd&gt; or the menu Insert →Procedure nodes → procedure call.&lt;/li&gt;
&lt;li&gt;A dialog will pop up:&lt;br /&gt;
    1. Click the button next to the label &amp;#8216;Procedure name&amp;#8217;.&lt;br /&gt;
    2. Another dialog will pop up. In this dialog choose the suite in which your procedure is located and select the wanted procedure in the tree.&lt;br /&gt;
    3. After clicking on the OK-button the wanted procedure is specified in the &amp;#8216;Procedure name&amp;#8217; textfield of the (first) pop up. If the procedure has some parameters, then these parameters are defined in the &amp;#8220;Variable definition&amp;#8221; table of the procedure call.&lt;/li&gt;
&lt;li&gt;Depending on the exact challenge, you may need to change the predefined parameters in the &amp;#8220;Variable definition&amp;#8221; table or specify a text in the &amp;#8216;Variable for return value&amp;#8217; textfield.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;E.g. at a certain point in our test suite, we want to get the current date in the format &amp;#8220;year-month-day&amp;#8221;. In order to solve this challenge, we can use the procedure named qfs.utils.getDate which is already predefined in the standard library (suite: qfs.qft). So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step 3.2: We need the procedure qfs.utils.getDate in the qfs.qft. So we select the tab qfs.qft and then the procedure qfs.utils.getDate in the tree.&lt;/li&gt;
&lt;li&gt;Step 4: By default this procedure is returning the current date in the format &amp;#8220;day.month.year&amp;#8221;. Because we want to get the current date in the format &amp;#8220;year-month-day&amp;#8221; we replace the text &amp;#8220;dd.MM.yyyy&amp;#8221; in the &amp;#8220;Variable definition&amp;#8221; table by &amp;#8220;yyyy-MM-dd&amp;#8221; (see &amp;#8216;format&amp;#8217; row).&lt;/li&gt;
&lt;li&gt;Step 4: We need to specify the name of the variable (e.g. currentDate) in which the current date should get saved. Therefore we simply specify currentDate in the &amp;#8216;Variable for return value&amp;#8217; textfield.&lt;/li&gt;
&lt;li&gt;After executing this procedure call we can access the content of this variable (and thus the string describing the current date). This can be done via the syntax $(nameOfTheVariable) (e.g. $(currentDate)) in the attributes of the different nodes or via rc.lookup in scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The complete procedure call is then looking like so:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Procedure Call QF-Test" src="/blog/resources/procedureCall_en.png" /&gt;&lt;/p&gt;
&lt;h2&gt;Remarks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The qfs.qft is public. This means everyone is allowed to see, analyze and learn from the different procedures in this suite. (You can open this suite via QF-Test. It is located in the folder &lt;code&gt;&amp;lt;path to the installation folder of qf-test&amp;gt;/qftest-$(version)/include/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;It may also be a good idea to place suites with procedures you are use in multiple testing projects in an own, independent, directory. You can then tell QF-Test about this directory via the &lt;code&gt;-libpath&lt;/code&gt; command line parameter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.qftest.com/doc/manual/en/user_stdlib.html#usec_stdlib"&gt;Introduction: The standard library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/en/standard-library.html"&gt;All procedures in the standard library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.qftest.com/doc/manual/en/opt_general.html#opt_libpath"&gt;Library path&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Capture &amp; Replay</title><link href="https://www.qftest.com/en/blog/article/capture-replay.html" rel="alternate"/><published>2019-02-18T00:00:00+01:00</published><updated>2019-02-18T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-02-18:/en/blog/article/capture-replay.html</id><summary type="html">&lt;p&gt;Even in early stages of development Capture &amp;amp; Replay offers a easy way to create tests. An early start of such regression tests and the assignment of unique names / IDs are the most important bases for efficient (due to stable recognition) test automation.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A significant part of all QF-Test tests have been &amp;ndash; at least partly &amp;ndash; created via the QF-Test capture and replay feature. Furthermore this feature has proven to be quite helpful &amp;ndash; especially for beginners. The opposite of capture-replay is test creation via scripting.&lt;/p&gt;
&lt;p&gt;The main goal of the capture &amp;amp; replay feature is to show/teach QF-Test the actions it should perform on the application it is testing. In the end QF-Test should be able to reproduce the shown steps (and thus to automatically perform the wanted test).&lt;/p&gt;
&lt;p&gt;After QF-Test established a connection to the application it should test, the record button in the taskbar will become enabled.&lt;/p&gt;
&lt;p&gt;Once the capture and replay feature (black square) got activated we can simply perform (green arrow) the wanted actions (e.g. click a certain component of the application). Once the stop recording button gets pressed QF-Test will add the recorded actions into the currently opened test suite.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=aQOLny8TDZw&amp;amp;list=PLsQ3ggMe67qgEd4HTruyIgzvdq0RYOzBX"&gt;Instead of long explanations, just take a look at QF-Test&amp;#8217;s operation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Remarks&lt;/h2&gt;
&lt;p&gt;Although recording a &amp;#8220;complete test case&amp;#8221; in one go is perfectly legal, it is often a good idea to implement the different actions step by step (modularization). This way it is easier to gasp a recording problem (in case one is occurring). Apart from the different recording settings, it is possible to influence the recording via resolver scripts, too.&lt;/p&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Request quotation</title><link href="https://www.qftest.com/en/blog/article/request-quotation.html" rel="alternate"/><published>2019-02-12T00:00:00+01:00</published><updated>2019-02-12T00:00:00+01:00</updated><author><name>Quality First Software</name></author><id>tag:www.qftest.com,2019-02-12:/en/blog/article/request-quotation.html</id><summary type="html">&lt;p&gt;You want to purchase QF-Test licenses or are interested in training or consulting? This works unbureaucratically.&lt;/p&gt;</summary><content type="html">&lt;p&gt;You have two possibilities to request a quote.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Send request via the following &lt;a href="https://services.qftest.com/en/license/purchase/"&gt;form&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Or contact our sales team va email to &lt;a href="mailto:sales@qftest.com"&gt;sales@qftest.com&lt;/a&gt; or telephone +49 8171 386 48-10 and mention the following information:&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Your data&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Telephone number&lt;/li&gt;
&lt;li&gt;Company name + invoicing address&lt;/li&gt;
&lt;li&gt;Country&lt;/li&gt;
&lt;li&gt;Email address&lt;/li&gt;
&lt;li&gt;VAT-ID for EU-Orders (outside Germany) or company-proof for orders from outside the EU&lt;/li&gt;
&lt;li&gt;Technical contact + his/her email address (When you send the request as a reseller, please tell us also the name of the client&amp;#8217;s company)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;QF-Test&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Number and type of licenses (Developer or Runtime)&lt;/li&gt;
&lt;li&gt;Desired technologies (Java Swing, SWT, JavaFX, Web, Android)&lt;/li&gt;
&lt;li&gt;Find an overview of all QF-Test offers &lt;a href="/en/product/pricing.html" title="Pricing"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Training or Consulting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Desired number of training days, face to face or online&lt;/li&gt;
&lt;li&gt;Desired number of consulting days, face to face or online&lt;/li&gt;
&lt;li&gt;Find an overview of all training/consulting offers here.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="license"/></entry><entry><title>Using QF-Test as Calculator</title><link href="https://www.qftest.com/en/blog/article/using-qf-test-as-calculator.html" rel="alternate"/><published>2019-02-05T00:00:00+01:00</published><updated>2019-02-05T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-02-05:/en/blog/article/using-qf-test-as-calculator.html</id><summary type="html">&lt;p&gt;Sometimes it is necessary to perform mathematical calculations in QF-Test, e.g. in order to calculate the expected height of a UI element in dependence of the complete window size or the exact clicking position for a mouse click.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Sometimes it is necessary to perform mathematical calculations in QF-Test, e.g. in order to calculate the expected height of a UI element in dependence of the complete window size or the exact clicking position for a mouse click.&lt;/p&gt;
&lt;p&gt;In order to do so we can use a &lt;a href="https://www.qftest.com/doc/manual/en/user_scripting.html#usec_scripting" title="Scripting node"&gt;Scripting node&lt;/a&gt;. Alternatively it is possible to use the &lt;code&gt;$[some mathematical expression]&lt;/code&gt; expression within node attributes. Like this the expression &lt;code&gt;$[3 + 3]&lt;/code&gt; will be replaced by 6. When we have stored the width of a current element in a QF-Test variable named &amp;#8220;canvasWidth&amp;#8221;, we can use &lt;code&gt;$[$(canvasWidth) &amp;ndash; 1]&lt;/code&gt; in order to click somewhere at the right border of a UI element:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Basic math in nodes QF-Test" src="/blog/resources/basicMathInNodes_en.png" /&gt;&lt;/p&gt;
&lt;p&gt;In fact the mathematical expression in the brackets is passed to a Jython eval statement. So it is also possible to perform more sophisticated operations within such an &amp;#8220;$[&amp;#8230;]&amp;#8221; expression:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$["Hello World"[1:5]]&lt;/code&gt; will be replaced by &amp;#8220;ello&amp;#8221;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$[rc.getLanguageName()]&lt;/code&gt; will be replaced by &amp;#8220;jython&amp;#8221;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$[re.split("\\d*", "foo123bar4foobar")[1]]&lt;/code&gt; will be replaced by &amp;#8220;bar&amp;#8221;. Please note, this statement will only work if you have previously executed a Jython Server Script that imports the re-module.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$[__import__('re').split("\\d*", "foo123bar4foobar")[1]]&lt;/code&gt; will be replaced by &amp;#8220;bar&amp;#8221; (this statement will work whether a Jython Server Script that imports the re-module has been executed or not).&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>QF-Test and Java client code coverage analysis</title><link href="https://www.qftest.com/en/blog/article/qf-test-and-java-client-code-coverage-analysis.html" rel="alternate"/><published>2019-01-30T00:00:00+01:00</published><updated>2019-01-30T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-01-30:/en/blog/article/qf-test-and-java-client-code-coverage-analysis.html</id><summary type="html">&lt;p&gt;A high percentage of executed code indicates a little probability of undetected software bugs.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Code_coverage"&gt;Code coverage analysis&lt;/a&gt; can be used in order to analyze to which degree the source code of a program gets executed during test execution. A high percentage of executed code suggests a lower chance of undetected software bugs.&lt;/p&gt;
&lt;p&gt;QF-Test alone does not provide a possibility to generate code coverage reports. However it is possible to combine QF-Test with Open Source Tools like JaCoCo. The integration of such tools make it possible to generate such code coverage reports for the teststeps performed by QF-Test.&lt;/p&gt;
&lt;h2&gt;How to integrate JaCoCo into a QF‑Test test run&lt;/h2&gt;
&lt;p&gt;In order to integrate JaCoCo into a QF-Test test run, you first need to extract the &amp;#8216;jacocoagent.jar&amp;#8217; file from the lib folder of the &amp;#8216;jacoco-$(version).zip&amp;#8217; file. This zip file can be downloaded from &lt;a href="https://www.eclemma.org/jacoco/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is easiest to integrate JaCoCo, if the Java application you are testing is a &lt;strong&gt;.jar&lt;/strong&gt; or a &lt;strong&gt;.class file&lt;/strong&gt;. In this case you simply need to locate the &amp;#8216;Start Java SUT client&amp;#8217; node that is starting your Java application. Then, specify&lt;/p&gt;
&lt;p&gt;-javaagent:&lt;path to jacocoagent.jar&gt;=destfile=&lt;path+filename to where jacoco should write its statistics&gt;&lt;/p&gt;
&lt;p&gt;in the &amp;#8220;parameter table&amp;#8221; of this node.&lt;/p&gt;
&lt;p&gt;In case you have a &lt;strong&gt;.exe&lt;/strong&gt; or a &lt;strong&gt;.bat&lt;/strong&gt; Java application, the above way will not work. In this case two Jython Server Scripts are needed. The first script with the content&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jacocojar = &amp;quot;&amp;lt;path to jacocoagent.jar&amp;gt;&amp;quot;  
destfile = &amp;quot;&amp;lt;path+filename to where jacoco should write its statistics&amp;gt;&amp;quot;  
rc.setProperty(&amp;quot;env&amp;quot;, &amp;quot;JAVA_TOOL_OPTIONS&amp;quot;, &amp;quot;-javaagent:%s=destfile=%s&amp;quot; % (jacocojar, destfile))`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;needs to get inserted before the &amp;#8216;Start SUT Client&amp;#8217; node and another more optional script with&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rc.setProperty("env", "JAVA_TOOL_OPTIONS", None)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;can be inserted after the &amp;#8216;Wait for client to connect&amp;#8217; node.&lt;/p&gt;
&lt;h2&gt;Remarks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Please note, that JaCoCo is not directly writing its report into the result file. The result file is written, when the application you are testing gets terminated!&lt;/li&gt;
&lt;li&gt;The JaCoCo result file is quite unreadable when opened with most text editors. So in order to analyze the statistics you probably want to use a plugin for your IDE.&lt;/li&gt;
&lt;li&gt;In order to gain control over the application you are testing, QF-Test is injecting some of its own classes into the application. As a result the result file does not only contain code coverage statistics for the classes of your application, but also for some QF-Test classes. Because you probably do not want to test QF-Test but your application, you can simply ignore the statistics created for QF-Test classes.&lt;/li&gt;
&lt;li&gt;Please be sure that the process that is executing your application has the rights to write the JaCoCo result file.&lt;/li&gt;
&lt;li&gt;When specifying a (Windows) path in a Jython/Groovy Script, please replace all &amp;#8220;&amp;#34; characters in the path by &amp;#8220;/&amp;#8221;.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"/><category term="how-to"/></entry><entry><title>Where do I get a trial license?</title><link href="https://www.qftest.com/en/blog/article/where-do-i-get-a-trial-license.html" rel="alternate"/><published>2019-01-25T00:00:00+01:00</published><updated>2019-01-25T00:00:00+01:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2019-01-25:/en/blog/article/where-do-i-get-a-trial-license.html</id><summary type="html">&lt;p&gt;You want to try QF-Test and start now? You cannot save your tests created with the QF‑Test demo version? Then request a trial license.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Simply trying a tool is always the best approach of testing it. So jut get your QF‑Test &lt;a href="https://services.qftest.com/en/license/request/"&gt;trial license&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;It is valid for four weeks after transmission and can be used by up to three users at the same time.&lt;/p&gt;
&lt;p&gt;You can try QF-Test directly with your own application, &lt;a href="/en/product/get-started-with-qf-test.html" title="Get started with QF-Test"&gt;get started&lt;/a&gt;!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We gladly support you with your POC.&lt;/li&gt;
&lt;li&gt;You can create first test cases and suites for your further use. They then can be transferred into the commercial license.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=yigFPkuEd7g&amp;amp;list=PLsQ3ggMe67qgEd4HTruyIgzvdq0RYOzBX&amp;amp;index=22&amp;amp;t=0s"&gt;&lt;img alt="Download &amp;amp; install QF-Test. Request trial license" src="/blog/resources/youtube-qf-test-download-install.png" /&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="blog"/><category term="license"/></entry><entry><title>Why should you test your software?</title><link href="https://www.qftest.com/en/blog/article/why-should-you-test-your-software.html" rel="alternate"/><published>2019-01-16T00:00:00+01:00</published><updated>2019-01-16T00:00:00+01:00</updated><author><name>Yann Spöri</name></author><id>tag:www.qftest.com,2019-01-16:/en/blog/article/why-should-you-test-your-software.html</id><summary type="html">&lt;p&gt;Software bugs are annoying &amp;ndash; for users and software manufacturers alike. The latter, however, should use bugs to continually improve the software, preferably from the beginning of development. Nevertheless, there will probably never really be flawless, right? Rather a philosophical question.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Software bugs are annoying &amp;ndash; for users and software manufacturers alike. The latter, however, should use bugs to continually improve the software, preferably from the beginning of development. Nevertheless, there will probably never really be flawless, right? Rather a philosophical question.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bugs" src="/blog/resources/bugs.png" title="Bugs" /&gt;&lt;/p&gt;
&lt;p&gt;The history of software programming has also been shaped by software bugs. Of course software bugs are also a cause for trouble &amp;ndash; especially if any bug is &lt;a href="https://en.wikipedia.org/wiki/Therac-25" title="Harmful software bugs"&gt;harmful&lt;/a&gt;, costly or has other &lt;a href="https://www.theregister.co.uk/2013/08/06/xerox_copier_flaw_means_dodgy_numbers_and_dangerous_designs/" title="Unpredictable software bugs"&gt;unpredictable consequences&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But that does not mean we cannot learn from each bug. Each analyzed and fixed bug gives us the opportunity to learn about what went wrong. This learning process can help us to avoid bugs in the feature. The learning process helps to find other similar undiscovered bugs in other softwares too.&lt;/p&gt;
&lt;p&gt;Here are two general ways to find software bugs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The first one is to look at the source code of a program, analyze each step and check if we can find source code patterns, that may be problematic. (Either by hand (software review) or to some extend also automatically.) For example we can check if we find any source code patterns in the code that are similar to source code patterns that have proven to be problematic. To some extend finding and analyzing such patterns is already done by the compiler. This is why we should take compiler warnings seriously.&lt;/li&gt;
&lt;li&gt;The other way to find software bugs is to simply execute the program/program-parts and test whether the program (under certain circumstances) crashes or shows an unexpected behavior.
Once an unexpected behavior gets noticed, the bug needs to get analyzed and fixed. An observation here is that while fixing a bug it is also possible to introduce new bugs into the code. This wired connection between the debugging/fixing effort and the number of bugs in the code is also represented in the &amp;#8216;Programmer’s Drinking Song&amp;#8217;:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;99 little bugs in the code.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;99 little bugs in the code.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Take one down, patch it around,&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;127 little bug in the code&amp;#8230;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So we are not only testing in order to find new bugs but also in order to ensure that once fixed bugs stay closed. But this means that &amp;ndash; whenever the source code gets changed &amp;ndash; every test case/test step needs to get redone. When this is done by hand, this may be a pity, boring and time consuming step. With a specialized software &amp;ndash; like QF-Test &amp;ndash; we can automatize our test steps.&lt;/p&gt;</content><category term="blog"/><category term="testing"/></entry><entry><title>Welcome to the QF-Test Blog</title><link href="https://www.qftest.com/en/blog/article/welcome-to-the-qf-test-blog.html" rel="alternate"/><published>2019-01-08T00:00:00+01:00</published><updated>2019-01-08T00:00:00+01:00</updated><author><name>Martina Schmid</name></author><id>tag:www.qftest.com,2019-01-08:/en/blog/article/welcome-to-the-qf-test-blog.html</id><summary type="html">&lt;p&gt;Our new QF-Test blog!&lt;/p&gt;</summary><content type="html">&lt;p&gt;We want to inform you about technical topics for beginners, advanced users or even specialists with this blog. The blog should replace the a bit out dated mailing list, answers technical questions as well as general topics like (license and purchasing questions, QFS&amp;#8230;).&lt;/p&gt;
&lt;p&gt;You have new topics in mind? &lt;a href="https://services.qftest.com/de/support/contact/?type=marketing"&gt;Please share them with us&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy reading!&lt;/p&gt;</content><category term="blog"/></entry></feed>