Appearance
UI-licious Test Framework
TAMI Studio uses the UI-licious Test Framework, an intuitive framework for writing automated UI tests that simulate real-world user interactions, from logging in to completing complex workflows.
What is the UI-licious Test Framework?
The core philosophy of the UI-licious Test Framework is Test User Journeys, not HTML. The framework is designed to help you write tests that describe the user actions and the expected behavior of the application in plain language. It abstracts away technical selectors and lets you write tests in natural language. This makes the framework accessible to QA engineers, developers, product managers, and even non-technical stakeholders, enabling everyone to understand and collaborate on how the application should behave.
Here's an example of a UI-licious test:
javascript
I.goTo("https://github.com/login")
I.fill("Username or email address", "<username>")
I.fill("Password", "<password>")
I.click("Sign in")
I.see("My repositories")Why use UI-licious?
UI-licious may be simple to start with, but it offers powerful features for robust and scalable UI testing.
Here are the key benefits of using the UI-licious Test Framework:
🧠 Easy to Learn: Write test scripts with little to no technical expertise — perfect for QAs, product managers, and new team members.
🗣️ Clear and Collaborative: Tests are written in plain language, making them easy to read, understand, and collaborate across QA, development, and product teams.
🛡️ Robust and Resilient: Smart targeting lets you use human-facing labels instead of brittle CSS or XPATH selectors, reducing test breakage from non-functional UI changes.
💻 Extensible and Reusable: Runs on JavaScript, allowing developers to use variables, loops, and custom functions for advanced test scenarios.
🌐 Cross-Browser Support: Run the same script for all major browsers - no need to maintain variations of a test for browser-specific quirks.
Smart Targeting
UI-licious lets you target elements using human-facing labels — instead of brittle CSS or XPath selectors — allowing you to write tests that reflect what users see, rather than how the UI is implemented.
For example, instead of writing:
javascript
// Example in Playwright
await page.click('button[data-testid="login-btn"]');You can simply write:
javascript
I.click("Sign in")This approach makes test scripts more readable, stable, and resilient to UI refactors that don’t affect user-visible behavior.
Under the hood, UI-licious smart targeting works by analyzing the rendered HTML at test execution time and scoring each element based on semantic relevance. Factors include:
- Tag names - Elements like
<button>and<a>are prioritized for click actions. <label>associations – Especially for form inputs- ARIA attributes – Such as
aria-labelandaria-labelledbyfor accessibility. - Descriptive attributes – Like
title,alt, andnamefor relevant elements. - Proximity to matching text – For example, input fields near a text element containing "Username" are likely targets for
I.fill("Username")
But if you need more precision, UI-licious still supports traditional CSS or XPath selectors when required:
javascript
I.click(".btn-toggle-mobile-menu")Smart Wait
The UI-licious Test Framework is eager to pass, and slow to fail — designed to reduce test flakiness caused by network latency or dynamic UI loading.
Each command includes built-in smart waits, meaning it will automatically retry attempts to execute the command until the expected condition is met or the timeout is reached.
By default, each command has a timeout of up to 15 seconds.
- Interaction commands (e.g
I.click()orI.fill()) will wait for a matching element to become visible before executing. If the element doesn’t appear in time, the command will fail. - Assertion commands like
I.see()orI.amAt()will repeatedly check until the condition becomes true, and fail when the timeout is reached.
You can customize the timeout dynamically within your test script using the TEST.commandTimeout setting. For example:
javascript
I.click("Log in")
TEST.commandTimeout = 60
I.see("Dashboard") // waits for up to 60 seconds for the text "Dashboard" to be visibleSmart waits help keep your tests resilient against temporary delays or inconsistent load times, without peppering your tests with manual waits.
Runs on Javascript
The UI-licious Test Framework is built on JavaScript, giving developers full access to the language’s power and flexibility. You can use variables, loops, and custom functions to write dynamic, reusable, and scalable test scripts for more complex scenarios.
Here’s an example of using JavaScript to loop through a shopping list and add multiple items to a cart:
javascript
const shoppingList = [
{ name: "Wireless Mouse", quantity: 2 },
{ name: "Laptop Stand", quantity: 1 },
{ name: "USB-C Hub", quantity: 3 },
{ name: "Bluetooth Keyboard", quantity: 2 }
]
I.goTo("https://acme-computer-store.com")
// Loop through the shopping list
shoppingList.forEach((product)=>{
I.fill("Search", product.name)
I.pressEnter()
if(I.see$(product.name)){
I.click(product.name)
for (var i = 0; i < product.quantity; i++) {
I.click("Add to cart")
}
}
})Co-routine synchronisation
In most JavaScript-based test frameworks like Playwright or Puppeteer, you need to handle asynchronous operations between the test script and the browser manually using await. This often results in verbose code, especially for non-developers or teams aiming for high readability.
UI-licious eliminates this complexity by using co-routine synchronization behind the scenes. All test commands are implicitly synchronized, so you don’t need to write await or worry about chaining promises. This keeps test scripts clean, concise, and readable.
With Playwright (async/await required):
javascript
await page.goto('https://github.com/login');
await page.fill('input[name="login"]', 'my-username');
await page.fill('input[name="password"]', 'my-password');
await page.click('input[name="commit"]');
await expect(page.locator('text=Your repositories')).toBeVisible();With UI-licious (co-routine synchronized):
javascript
I.goTo("https://github.com/login")
I.fill("Username or email address", "my-username")
I.fill("Password", "my-password")
I.click("Sign in")
I.see("Your repositories")Benefits of Co-routine Synchronization:
✅ No
async/awaitneeded – Makes tests cleaner and easier to write and maintain.🧠 Improves readability – Especially helpful for non-technical stakeholders.
🧹 Less boilerplate – Focus on what you're testing, not how JavaScript handles execution.
This abstraction lets you focus on writing expressive tests that describe user behavior, rather than managing technical complexity.
Learn more
👉 Want to dive deeper? Explore advanced scripting techniques, test organization, and reporting tools in the UI-licious documentation.