Introduction

SchnauzerUI is a human readable DSL for performing automated UI testing in the browser. The main goal of SchnauzerUI is to increase stakeholder visibility and participation in automated Quality Assurance testing.

SchnauzerUI lets non-programmers create automated tests from start to finish, without out ever needing to involve a developer. To demonstrate, lets look at the SchnauzerUI code necessary to search for cats on youtube.

url "https://youtube.com"
locate "Search" and type "cats" and press "Enter"

This is the only code necessary to write in order to run the test in a live browser and generate a test report.

About this document

We are subscribing to the Divio documentation system, so this document is composed of four sections:

  1. Tutorials: Focused on learning SchnauzerUI. Hands on and closely guided.
  2. How-To Guides: Focused on solving specific problems with SchnauzerUI. We hope to accumulate a lot of these, so don't be afraid to submit a Github issue asking for help, it helps us as much as it does you.
  3. Reference: A complete list of all available functionality of SchnauzerUI.
  4. Explanation: Covers how SchanzuerUI works under the hood and why it was designed that way. Great place to start if you're considering forking or contributing to SchnauzerUI.

To get started, checkout the quickstart tutorial

Quickstart

Installation

Warning: The below install process is quite technical and involves using your computer's terminal.

To install Schnauzer UI, you'll need the rust toolchain installed. Then run cargo install schnauzer_ui. After installing, type sui --help and take a quick look at the options.

You will also need a webdriver compliant process running to run your tests against. Schnauzer UI currently supports firefox and chrome. geckodriver, chromedriver, or a selenium standalone should work just fine. Make a note of what port you're running the process on.

If you've never worked with any of these before:

  • Try installing either geckodriver or chromedriver however you normally install packages (brew, apt, etc.) depending on whether you want to test in Firefox or Chrome respectively.
  • If that's not an option, you can manually download any of these as binaries, put them somwhere in your path, and run them.
  • If that all sounds like too much, and you're okay using Firefox, you can build geckodriver from source with cargo install geckodriver and then run it.

Running the REPL for the first time

To start a REPL, first make sure your webdriver process is running. Then make yourself a folder called sui_tutorial (or whatever you like) and open a terminal there. Type sui -b <the browser> -p <the port>. For example, sui -b chrome -p 9515. (Note: Schnauzer UI defaults to using firefox on port 4444, so the commands sui and sui -b firefox -p 4444 are equivalent).

You should see 1. a browser launch and 2. a prompt appear on the terminal asking for the name of your test. Go through the start up options until you are prompted for a command. Then type url "https://youtube.com" and hit enter. The browser should navigate to YouTube. You will then be prompted for whether you want to save the command. The default is yes, so simply hit enter.

Congrats! You're up an running with Schnauzer UI. Feel free to try commands from the reference.

When you're ready to quit, type exit as a command and hit enter. You will be asked if you want to save your script. Simply type yes and hit enter.

Running a saved script file

There should now be a Schnauzer UI script saved as a file ending in .sui in your current directory. cat the file or open it up in your favorite text editor to take a look inside. You should see your saved commands there. This is your brand new Schnauzer UI test script!

To run the script, type sui -f <path-to-the-file> along with the port and browser arguments if they are not the default. The browser should launch again and run the entire script, then generate some test results. There should now also be an HTML file, a JSON file, and a screenshots folder with any screenshots taken during the test run.

  • The HTML file is the standard formatting of the test report. You can open it in a browser to see the report.
  • The JSON file is the same information in JSON format, in case you want to create custom styled reports or use the test result data programatically.

Statements

Don't see functionality you were looking for? File an issue on our github and we'll consider implementing it for you. Or feel free to file a pull request.

Comment

A comment statement is the simplest statement in Schnauzer UI. Create a comment by typing # and then the text you'd like. Comments are automatically added to the test run log for reference. It's good practice to separate chunks of a Schnauzer UI script with comments to keep it readable.

Ex. Commenting a chunk of code.

# This part of the script performs x action

For programmers: Schnauzer UI doesn't have units of code that can be extracted and reused like "functions". This makes good comments really important for keeping longer scripts organized and readable. We don't have functions or modules because it's a priority that each script be a straightforward linear description of a browser based process, which can be easily passed around via tools like Jira, Teams, Slack, etc.

Save As

The save as command will save some text as a variable for use later in the script.

Ex. Begin a script with some important info

save "test@test.com" as username

Command Statement

A command statement consists of a one or more commands connected by the and keyword. This is the bread and butter of your scripts.

Ex. Locate and click a button

locate "Submit" and click

If Statement

An if statement is used for conditional actions. The statement takes a command as a predicate, and executes the body only if the command succeeds without error.

Ex. Dismissing a popup.

if locate "Confirm" then click

Catch Error

Schnauzer UI provides the catch-error statement for simple error handling. Whenever a command produces an error (except in an if condition), the script will jump ahead to the nearest catch-error: token. This lets you handle an error how you want. Some commands to keep in mind are screenshot, refresh, and try-again. If you don't make use of catch-error, scripts will simply exit when they encounter errors (which is good for a lot of testing use cases but not good for RPA use cases).

Ex. Taking a screenshot from a script that reproduces a bug.

catch-error: screenshot

Under

An under statement changes the way locators work for a single line of code. It lets the locator start searching for html by radiating out from a given element rather than starting at the top of the HTML document. It's especially useful for locating elements by visual text when that text is present multiple places on the page. The scope of the search is not restricted to elements contained by the located element. Locators will still search the whole page, but they will start searching where you tell them. Consider the following HTML

<h1>Desired Text</h1>
<div class="container">
    <div class="nav-title">
        <h3>Navigation</h3>
    </div>
    <div class="nav-body">
        <a href="https://somesite.com">Desired Text</a>
        ... more links
    </div>
</div>

the command

under "Navigation" locate "Desired Text" and click

will click the navigation link, not the level one heading. This is not because the link is "under" the h3 element, but because they are close together. The locate command searches children of the h3, then children of the div with class nav-title, then children of the div with class container before succeeding.

Under Active Element

Works the same as the Under statement, but begins searching under the "active" element (the last element interacted with).

Ex. Locate an element near the last element interacted with.

under-active-element locate subElement and click

Commands

url

The url command navigates to a spcific url. It can take either a full url or a slug as an argument

Ex. Navigate to complete url

url "https://youtube.com"

Ex. Navigate to a slug /posts

url "posts"

locate

The locate command finds a web element and scrolls it to the top of the viewport to interact with.

Ex. Locate a submit button

locate "Submit"

The locate command uses precedence to determine how to use the provided locator.

  • Match a placeholder or partial placeholder
  • Match text or partial text
  • Match title attribute
  • Match aria-label
  • Match id attribute
  • Match name attribute
  • Match class attribute or partial class attribute
  • Match an XPath

locate-no-scroll

The locate-no-scroll command is the same as the locate command, but does not scroll the element from where it is. Useful for when scrolling an element to the top of the viewport causes it to be covered by a navbar or some other element.

click

The click command performs a click at the location of the located element. This helps to avoid click intercept issues with complex components.

Ex. Locate a button and click it

locate "login-btn" and click

type

In general, the type command will send text to the located element. In reality, the type command will click a located element then begin typing in the active element! This is usually the same thing, but better for complex UI components which switch the active element on their own.

Ex. Type in a username

locate "Username" and type "test@test.com"

refresh

The refresh command simply refreshes the page

screenshot

The screenshot command will capture a screeshot of the current window

read-to

The read-to command will save the text of a web element to a variable. Useful for things like dynamically generated names etc.

Ex. Read the number of search results to a variable

locate "result-stats" and read-to mySearchResults

press

The press command is used to perform keyboard actions. The kepresses are registered against the currently selected web element, so it's mainly useful for things like hitting Enter from a search box.

Ex. Press enter when logging in.

locate "password" and type myPassword and press "Enter"

chill

The chill command causes the script to pause for the provided number of seconds. Useful for waiting for some process to finish. (Note: Commands by default have a one second wait between execution. Explicitly managing waits is complicated, and we opted for a simpler approach. Generally this command will not be necessary. If you are waiting for some transition on the page to take place, consider using the locate command to automatically wait for an element to signal the page is ready. For example, after logging into a website, rather than using the chill command, use locate to find some element of the loaded dashboard to verify that the page has loaded.)

Ex. Wait 10 seconds.

chill "10"

drag-to

The drag-to command uses javascript to simulate a drag and drop event, dragging the currently located element to the element matching the provided locator.

Ex. Imagine mapping headers to the correct column of an uploaded file.

locate "email" and drag-to "@"

select

The select command will select (by text) one of the options in a select element. Note. This command will also work if the currently located element is an option in the given select element. This makes it much easier to locate and use select elements using only the displayed option text.

Ex. Login as an admin user.

locate "Select Role" and select "Admin User"

upload

The upload command performs a basic file upload on an html input element of type file. The located element must be an input element. Very often, custom component is used which performs the javascript to do the upload, which means you'll have to look in the html for the hidden input you actually want to upload to.

Ex. Upload file

locate "//input[@id='file-input']" and upload "./screenshots/main_screenshot_2.png"

Note: We are locating the input by xpath because it's not actually displayed on the page. Smart locators only return elements which are currently displayed.

accept-alert and dismiss-alert

The accept-alert and dismiss-alert commands accept and dismiss alerts.

Ex. Accept cookie alert

# Accept cookie alert
accept-alert

What Schnauzer UI knows

Schnauzer UI tries to be smart about the way it interacts with HTML, so the person writing the test rarely has to look the underlying HTML of a page. This page outlines some of the strategies it takes to achieve that.

Smart Swap

Schnauzer UI tries to make it easy to locate and interact with web elements based on only visible information on the page (i.e. without having to look at HTML). One way it does this is by acknowledging that HTML elements are often used together in common patterns (many are designed this way).

When Schnauzer UI can, it will try to make a judgement call about the element you actually want to interact with vs the element you were able to locate on the page, and swap them out for appropriate commands.

For input, textarea, and select elements, if you are able to locate

  • A span or label containing the element
  • A span or label directly preceding the element
  • A span or label roughly near the element

then Schnauzer UI will swap the element on click and type commands

Additionally, select commands will do the same, as well as check to see if the currently located element is an option inside a select.

Clicking and Typing

Often times, the real world components we're testing are fancy. The produced HTML can have several layers that shift around while we interact with them. Schnauzer UI tries to make these components easier to test by acting as closely to a person as possible.

The click command does not click the located element. It registers a click event at the center of the elements location on the page. This helps to reduce headaches around which element should actually be clicked (in my experience, dealing with click intercepted errors is half of day to day selenium use).

Similarly, the type command doesn't type into the located element. It clicks the currently located element, and then types into the active element. This is what people do, and it helps with fancy search dropdowns and things that use javascript to swap out elements underneath you after registering a click.

Under

HTML is fundamentally a tree structure, and it's structure isn't necessarily related to the way a webpage is layed out (CSS and JavaScript have a tendency to move things around). Often though, HTML elements are composed something like this.

<div id="container">
    <div id="header">
        I am a heading of some section of this page
    </div>
    <div id="content">
        <p>I am some content</p>
        <div id="sub-content">
            ... more html
        </div>
    </div>
</div>

Pieces of HTML that related to each other get wrapped up in a container, with some kind of title/heading at the top followed by the content of the component. Often the HTML is more deeply nested than this. For example, here's the sample HTML for a Bootstrap card

<div class="card" style="width: 18rem;">
  <img class="card-img-top" src="..." alt="Card image cap">
  <div class="card-body">
    <h5 class="card-title">Card title</h5>
    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
  </div>
  <ul class="list-group list-group-flush">
    <li class="list-group-item">Cras justo odio</li>
    <li class="list-group-item">Dapibus ac facilisis in</li>
    <li class="list-group-item">Vestibulum at eros</li>
  </ul>
  <div class="card-body">
    <a href="#" class="card-link">Card link</a>
    <a href="#" class="card-link">Another link</a>
  </div>
</div>

Schnauzer UI takes advantage of this common organization strategy when helping you locate elements with visual information. The under command changes the way locators work, so that the search for your element begins where you want it to.

One could type under "Card title" locate "Card link" and click, and expect the Card link to be correctly selected and clicked. Schnauzer UI will start with the h5 element and walk up the tree to the parent element, until the current element contains the one it's looking for. This is especially useful for buttons/labels/placeholders that get repeated several places on a page. Think "add to cart", "see more...", etc. on gallery type pages.