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:
- Tutorials: Focused on learning SchnauzerUI. Hands on and closely guided.
- 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.
- Reference: A complete list of all available functionality of SchnauzerUI.
- 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
orchromedriver
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.