Pickleib is a comprehensive, polymorphic test automation utility library designed to streamline Web, Mobile, Desktop, API and Database testing.
It acts as a robust wrapper around Selenium and Appium, allowing you to write interaction-agnostic code that works across platforms. It offers a unique "Hybrid" Page Object Model approach, letting you choose between a classic Java implementation or a "Low-Code" JSON-based element definition.
To see Pickleib in action, and to use it as a no code solution, check out the π test-automation-template
- π Polymorphic Interactions: Write tests that run on web, mobile & desktop platforms using a unified interface.
- ποΈ Hybrid Page Object Model: Classic Java POM with
@FindBy, or Low-Code JSON-based selectors β your choice. - π Built-in Step Definitions: 68 pre-built Cucumber steps covering click, fill, verify, scroll, wait, select, context, and more.
- π€ Annotation-Driven Runner:
@Pickleib,@PageObject,@ContextValueannotations eliminate boilerplate. - π Smart Driver Management: Automated
WebDriverandAppiumDriverlifecycle handling. - β€οΈβπ©Ή Self-Healing Utilities: Retry mechanisms for
StaleElementReferenceExceptionandFluentWaitsynchronization. - π§³ Context Management: Global
ContextStorefor sharing data between steps and configuring runtime variables. - π§΅ Thread-Safe Parallel Execution:
ThreadLocaldriver singletons β run tests in parallel without interference. - π οΈ Cross-Functional Testing: API testing (Wasapi), Database (JDBC), Email client, Web Data Layer validation.
Add the following dependency to your pom.xml:
<dependency>
<groupId>io.github.umutayb</groupId>
<artifactId>pickleib</artifactId>
<version>2.1.0</version>
</dependency>Pickleib ships with a Claude Code agent skill for AI-assisted test generation. The skill is automatically extracted to skills/pickleib/SKILL.md in your project the first time PickleibRunner loads β no additional configuration needed. Just add Pickleib as a dependency and the skill becomes available.
Add skills/ to your .gitignore β the skill is extracted from the JAR at runtime.
There are two main approaches to defining your elements. Both give you access to the same built-in step library.
| Approach | Best For | Element Definition | Java Code Needed |
|---|---|---|---|
| JSON Repository (Low-Code) | Quick start, small teams, simple pages | page-repository.json |
Minimal (just CommonSteps + Hooks) |
| Page Objects (Classic POM) | Large projects, complex interactions, IDE support | Java classes with @FindBy |
Page classes + ObjectRepository |
Pick the one that fits your workflow β or mix both in the same project.
Get tests running with zero page classes. Define elements in JSON, add a Hooks file, and write Gherkin.
{
"pages": [
{
"name": "LoginPage",
"platform": "web",
"elements": [
{
"elementName": "usernameInput",
"selectors": { "web": [{ "css": "#user-name" }] }
},
{
"elementName": "passwordInput",
"selectors": { "web": [{ "css": "#password" }] }
},
{
"elementName": "loginButton",
"selectors": { "web": [{ "id": "login-button" }] }
},
{
"elementName": "welcomeMessage",
"selectors": { "web": [{ "css": ".welcome" }] }
}
]
}
]
}Each page has a name, platform, and a list of elements. Each element supports multiple selector types (css, id, xpath, accessibilityId) and platform-specific selectors (web, android, ios).
import io.cucumber.java.*;
import pickleib.web.driver.PickleibWebDriver;
public class Hooks {
@Before
public void setup() {
PickleibWebDriver.initialize();
}
@After
public void teardown() {
PickleibWebDriver.terminate();
}
}@Web-UI
Scenario: Login flow
* Navigate to url: https://example.com
* Fill input usernameInput on the LoginPage with text: admin
* Fill input passwordInput on the LoginPage with text: secret
* Click the loginButton on the LoginPage
* Verify the text of welcomeMessage on the LoginPage contains: Welcome@Pickleib auto-detects page-repository.json at the default location β no CommonSteps class needed:
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.extension.ExtendWith;
import pickleib.annotations.Pickleib;
import pickleib.runner.PickleibRunner;
@RunWith(Cucumber.class)
@Pickleib
@ExtendWith(PickleibRunner.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = {"steps", "pickleib.steps"},
plugin = {"json:target/reports/Cucumber.json"}
)
public class TestRunner {}Important: Add "pickleib.steps" to the glue path. This activates the built-in step definitions.
mvn test -Dcucumber.filter.tags="@Web-UI" -Dbrowser=chromeUse standard Java classes with @FindBy annotations for IDE autocomplete, compile-time safety, and custom page methods.
package pages;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import pickleib.web.PickleibPageObject;
public class LoginPage extends PickleibPageObject {
@FindBy(id = "user-name")
public WebElement usernameInput;
@FindBy(css = "#password")
public WebElement passwordInput;
@FindBy(id = "login-button")
public WebElement loginButton;
@FindBy(css = ".welcome")
public WebElement welcomeMessage;
}Register your page classes in a single class. Pickleib uses reflection to scan this.
package common;
import pages.*;
import pickleib.utilities.interfaces.repository.PageObjectRepository;
public class ObjectRepository implements PageObjectRepository {
public LoginPage loginPage;
public DashboardPage dashboardPage;
// Add all page objects as fields
}import common.ObjectRepository;
import pickleib.utilities.steps.PickleibSteps;
public class CommonSteps extends PickleibSteps {
public CommonSteps() {
super(ObjectRepository.class);
}
}Same as the JSON approach β one class gives you all 68 built-in steps.
import io.cucumber.java.*;
import pickleib.web.driver.PickleibWebDriver;
public class Hooks {
@Before
public void setup() {
PickleibWebDriver.initialize();
}
@After
public void teardown() {
PickleibWebDriver.terminate();
}
}The Gherkin syntax is identical regardless of which approach you use:
@Web-UI
Scenario: Login flow
* Navigate to url: https://example.com
* Fill input usernameInput on the LoginPage with text: admin
* Fill input passwordInput on the LoginPage with text: secret
* Click the loginButton on the LoginPage
* Verify the text of welcomeMessage on the LoginPage contains: WelcomeAll steps work with both JSON and Page Object approaches. Add pickleib.steps to your Cucumber glue path to activate them.
| Step | Description |
|---|---|
Navigate to url: {url} |
Open a URL |
Navigate to test url |
Open the URL from test-url context key |
Go to the {pagePath} page |
Navigate to a relative path |
Refresh the page |
Reload the current page |
Navigate browser backwards/forwards |
Browser back/forward |
Switch to the next tab |
Switch to the next browser tab |
Switch back to the parent tab |
Return to the original tab |
Save current url to context |
Store current URL in ContextStore |
| Step | Description |
|---|---|
Click the {element} on the {Page} |
Click an element on a page |
Click button with {text} text |
Click by visible text |
Click listed element {name} from {list} list on the {Page} |
Click element from a list |
If present, click the {element} on the {Page} |
Click only if element exists |
If enabled, click the {element} on the {Page} |
Click only if element is enabled |
Click towards the {element} on the {Page} |
Scroll-to-click |
| Step | Description |
|---|---|
Fill input {element} on the {Page} with text: {value} |
Fill a text input |
Fill input {element} on the {Page} with verified text: {value} |
Fill and verify the value was set |
Fill form input on the {Page} |
Fill multiple inputs from a table (see below) |
Select option {text} from {element} on the {Page} |
Select from a dropdown |
Form fill table format:
* Fill form input on the LoginPage
| element | input |
| usernameInput | admin |
| passwordInput | secret || Step | Description |
|---|---|
Verify the text of {element} on the {Page} to be: {text} |
Exact text match |
Verify the text of {element} on the {Page} contains: {text} |
Partial text match |
Verify presence of element {element} on the {Page} |
Element exists in DOM |
Verify absence of element {element} on the {Page} |
Element not in DOM |
Verify that element {element} on the {Page} is in {state} state |
Check enabled/displayed/selected/disabled/absent |
Verify that element {element} on the {Page} has {value} value for its {attribute} attribute |
Check attribute value |
Verify the url contains with the text {text} |
URL assertion |
| Step | Description |
|---|---|
Wait {n} seconds |
Hard wait |
Wait for element {element} on the {Page} to be visible |
Wait until visible |
Wait for absence of element {element} on the {Page} |
Wait until gone |
Wait until element {element} on the {Page} has {value} value for its {attribute} attribute |
Wait for attribute |
| Step | Description |
|---|---|
Scroll up/down/left/right using web/mobile driver |
Directional scroll |
Scroll until listed {element} from {list} is found on the {Page} |
Scroll to find element |
Center the {element} on the {Page} |
Scroll element into center view |
| Step | Description |
|---|---|
Update context {key} -> {value} |
Store a key-value pair |
Save context value from {key} context key to {newKey} |
Copy a context value |
Acquire the {attribute} attribute of {element} on the {Page} |
Save attribute to context |
Use CONTEXT-{key} in any step value to reference stored context values:
* Update context testUser -> admin
* Fill input usernameInput on the LoginPage with text: CONTEXT-testUser| Step | Description |
|---|---|
Upload file on input {element} on the {Page} with file: {path} |
File upload |
Execute JS command: {script} |
Run JavaScript |
Set window width & height as {w} & {h} |
Resize browser |
Add the following cookies: |
Add cookies from table |
Delete cookies |
Clear all cookies |
Marks a test class for automatic element repository setup. When used without parameters, auto-detects the approach:
- Looks for
src/test/resources/page-repository.jsonβ if found, uses JSON repository - Otherwise scans for
@PageObject/@ScreenObjectclasses in thepagespackage - If neither is found, logs a warning with setup instructions
@Pickleib // auto-detect β just works
@ExtendWith(PickleibRunner.class)
public class MyTest { ... }
// Or explicit configuration:
@Pickleib(scan = {"com.myapp.pages", "com.myapp.screens"})
@Pickleib(pageRepository = "custom/path/repo.json")| Attribute | Description | Default |
|---|---|---|
scan |
Packages to scan for @PageObject / @ScreenObject classes |
{} (auto: pages) |
pageRepository |
Path to page-repository.json |
"" (auto: src/test/resources/page-repository.json) |
builtInSteps |
Enable built-in Cucumber step definitions | true |
Mark any class as a page object β no inheritance required:
@PageObject
public class LoginPage { ... }
@PageObject(platform = Platform.ios, name = "Login")
public class LoginPageIOS { ... }| Attribute | Description | Default |
|---|---|---|
platform |
Target platform (Platform enum) |
Platform.web |
name |
Custom registry name (defaults to class name) | "" |
Mark a class as a mobile screen object:
@ScreenObject
public class HomeScreen { ... }| Attribute | Description | Default |
|---|---|---|
platform |
Target platform (Platform enum) |
Platform.android |
name |
Custom registry name (defaults to class name) | "" |
Inject values from the ContextStore directly into fields. Supports {{key}} replacement patterns:
@ContextValue("test-url")
private String testUrl;
@ContextValue(value = "timeout", defaultValue = "15000")
private long timeout;Supported platform values: web | android | ios | macos | windows
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Test Project β
β β
β ββββββββββββ ββββββββββββ βββββββββββββββββ βββββββββββββββ β
β β Feature β β Hooks β β Page Objects β β Test Runner β β
β β Files β β β β(Java or JSON) β β β β
β β(.feature)β β(@Before/ β β β β (@Pickleib/ β β
β β β β @After) β β β β @CukeOpts) β β
β ββββββ¬ββββββ ββββββ¬ββββββ βββββββββ¬ββββββββ ββββββββ¬βββββββ β
β β β β β β
βββββββββΌβββββββββββββββΌβββββββββββββββββΌβββββββββββββββββββΌββββββββββ
β β β β
βββββββββΌβββββββββββββββΌβββββββββββββββββΌβββββββββββββββββββΌββββββββββ
β βΌ βΌ βΌ βΌ β
β Pickleib β
β ββββββββββββ ββββββββββββ βββββββββββββββββββββ βββββββββββ β
β βBuiltIn β βPickleib β β PageObjectRegistryβ βPickleib β β
β βSteps β βWebDriver β β or PageObjectJson β βRunner β β
β β(68 steps)β β(ThreadL) β β(ElementRepository)β β(ExtendW)β β
β ββββββ¬ββββββ ββββββββββββ ββββββββββ¬βββββββββββ βββββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β InteractionBase β β
β β βββββββββββββββββ ββββββββββββββββββ β β
β β βWebInteractionsβ βPlatformInteractβ β β
β β β (Selenium) β β (Appium) β β β
β β ββββββββ¬βββββββββ βββββββββ¬βββββββββ β β
β βββββββββββββΌββββββββββββββββββββββββββββΌβββββββββββββββββββ β
β β β β
β βββββββββββββΌββββββββββββββββββββββββββββΌβββββββββββββββββββ β
β β Utility Helpers β β
β β βββββββββββ ββββββββββββ βββββββββββββββββ β β
β β βClickHlp β βInputHelp β βElementStateHlpβ β β
β β ββββββ¬βββββ ββββββ¬ββββββ βββββββββ¬ββββββββ β β
β βββββββββΌβββββββββββββββΌβββββββββββββββββββΌβββββββββββββββββ β
β ββββββββββββββββΌβββββββββββββββββββ β
β βΌ β
β ββββββββββββββββ β
β β RetryPolicy β β
β ββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Pickleib is configured via pickleib.properties in your resources directory. These are loaded into the global ContextStore.
browser=chrome
headless=true
driver-timeout=15000
element-timeout=15000
frame-width=1920
frame-height=1080
delete-cookies=true| Property | Description | Default |
|---|---|---|
browser |
chrome, firefox, safari, edge |
chrome |
headless |
Run without a UI | false |
driver-timeout |
Implicit wait in ms | 15000 |
element-timeout |
Element interaction timeout in ms | 15000 |
frame-width / frame-height |
Browser window size | 1920 / 1080 |
driver-maximize |
Maximize window on startup | false |
delete-cookies |
Clear cookies before each test | false |
selenium-grid |
Use a remote Grid | false |
hub-url |
Grid hub URL | "" |
mobile-mode |
Chrome mobile emulation | false |
emulated-device |
Device profile for emulation | iPhone12Pro |
device=MyApp
config=src/test/resources/configurations
use-appium2=true
address=0.0.0.0
port=4723Create a JSON capability file at {config}/{device}.json:
{
"platformName": "iOS",
"automationName": "XCUITest",
"deviceName": "iPhone 14",
"bundleId": "com.company.app",
"app": "src/test/resources/apps/MyApp.app"
}Driver singletons use ThreadLocal β parallel execution is safe out of the box.
mvn test -Dcucumber.execution.parallel.enabled=true -Dcucumber.execution.parallel.config.fixed.parallelism=4This repository includes a sample test website:
docker-compose up --build -dAccess at π http://localhost:7457