Home / Modules / yoja-selenium

yoja-selenium

Browser-driven tests with a fluent TestBuilder: pick a browser, start an embedded server, run named JS exports as test steps, capture browser logs, integrate as JUnit 6 dynamic tests.

Installation

testImplementation 'com.easygoingapi:yoja-selenium:VERSION'

Requires a matching Selenium WebDriver on the test classpath (chromedriver, geckodriver, msedgedriver), either on PATH or via Selenium Manager.

Quick start

import com.easygoingapi.yoja.selenium.*;
import org.junit.jupiter.api.*;
import java.util.stream.Stream;

class TaskBoardTest {

    @TestFactory
    Stream<DynamicNode> scenarios() {
        return TestBuilder.builder()
            .browser(Browser.builder(Browser.CHROME)
                            .mode(Browser.Mode.HEADLESS)
                            .build())
            .startYojaWeb()
            .loadYwAssert()
            .testJsUnit("/jsUnitSyncTest.js",
                        List.of("titleIsTaskManager", "loginFormIsPresent"))
            .testAsyncModule("/jsUnitAsyncTest.js")
            .stream();
    }
}

Browser

Browser enum

Supported browsers. Each value maps to a Selenium WebDriver implementation.

Values
ValueDescription
CHROME · FIREFOX · EDGESupported browsers; require the matching WebDriver on PATH or via Selenium Manager
API
SignatureReturnsDescription
getWebDriverClass()Class<WebDriver>Selenium driver class for this browser
static builder(Browser)Browser.BuilderStart a Config builder
Browser.Mode
ValueDescription
HEADLESSNo visible window
HEADFULVisible window
DEBUGGERPins all timeouts to 1 hour and opens devtools
Browser Mode Debugger

Browser.Mode.DEBUGGER mode flips three switches at browser-launch time:

SwitchFirefoxChromeEdge
DevTools opened on startup-devtools arg--auto-open-devtools-for-tabs--auto-open-devtools-for-tabs
Window maximizedyes (after 1 s warm-up)no (currently disabled)yes (after 2 s warm-up)
scriptTimeout
pageLoadTimeout
implicitlyWait
Duration.ofHours(1)samesame

The 1-hour timeouts matter most: without them, pausing at a Java breakpoint for more than the default 10 s would make Selenium abort the in-flight script and break the session. They give you room to inspect state at length without losing the browser context.

Browser.Mode.DEBUGGER mode does not pause on its own — it only prepares the environment. To actually pause, set a Java breakpoint in your IDE on the line of interest, or insert selenium.debugger() / builder.debugger() as a marker; the auto-opened DevTools then let you inspect the live DOM in parallel.

Which exception to break on. debugger() works by throwing — and immediately swallowing — a com.easygoingapi.yoja.selenium.Debugger exception (a RuntimeException). It never propagates, so it can never fail your test; its only purpose is to be a stable target for the IDE. To make debugger() pause, add an exception breakpoint on com.easygoingapi.yoja.selenium.Debugger (IntelliJ: Run ▸ View Breakpoints ▸ + ▸ Java Exception Breakpoint; Eclipse: Add Java Exception Breakpoint, keep "Caught" since the throw is caught inline) — every debugger() call then suspends the JVM at that throw while the browser stays alive thanks to the 1-hour timeouts.

Browser.Config (Builder)
SignatureReturnsDescription
Builder.mode(Mode)Builder
Builder.timeout(Duration)BuilderDefault wait timeout for element lookups
Builder.build()Browser.Config
getBrowser()Browser
getMode()Mode
getTimeout()Duration

SeleniumService

SeleniumService class (AutoCloseable)

High-level wrapper around a WebDriver, tailored to driving Yoja-Web pages.

Factory
SignatureReturnsDescription
static newInstance(Browser.Config)SeleniumServiceLaunch the browser with the right options
webDriver()WebDriverUnderlying Selenium driver
javascriptExecutor()JavascriptExecutor
Navigation
SignatureReturnsDescription
getHttpPage(HttpUrl | String)voidNavigate the browser to the given URL
getHttpPage(Duration, HttpUrl | String)voidNavigate with an explicit wait timeout
reload()voidReload the current page
reload(ScriptOption)voidReload and re-inject helpers
await(Duration)voidPause execution for the given duration
resizeWindow(int w, int h)voidSet the browser window dimensions
Script execution
SignatureReturnsDescription
executeScript(String | Path [, args])ObjectExecute synchronous JS
executeScript(Duration, String | Path, args)ObjectWith an explicit timeout
executeAsyncScript(String | Path [, args])ObjectExecute async JS (must call the injected done callback)
repeatScript(Duration until, String, args)ObjectRe-execute until a truthy value is returned or the timeout elapses
repeatAsyncScript(Duration, String, args)ObjectAsync variant of repeatScript
DOM queries
SignatureReturnsDescription
firstTag(css)WebElementFirst matching element (waits for presence)
firstTag(Duration, css)WebElementWith an explicit wait timeout
firstTagFrom(WebElement, css)WebElementScoped to a given element
findTags(css)List<WebElement>All matching elements
findTagsFrom(WebElement, css)List<WebElement>Scoped to a given element
Storage accessors
SignatureReturnsDescription
localStorage(key)StringRaw value from window.localStorage
localStorage(key, Storage)StringStorage.date / type / value — extract a specific field from the stored JSON
sessionStorage(key [, Storage])StringSame for window.sessionStorage
Logs & helpers
SignatureReturnsDescription
logs()List<Log>All browser-side log entries collected so far
clearLogs()voidDiscard captured log entries
saveLogs()voidPersist logs to disk
printLogs()voidPrint logs to stdout
loadJavascript(String)voidInject a JS snippet into the current page
loadYwAssert()voidInject the ywAssert.js assertion helper
debugger()voidIDE breakpoint hook
close()voidQuit the WebDriver

TestBuilder

TestBuilder class

Fluent orchestrator. Builds a list of test steps, then runs them via execute() or exposes them as JUnit dynamic nodes via stream().

Factory
SignatureReturnsDescription
static builder()TestBuilder
Configuration
SignatureReturnsDescription
browser(Browser.Config)TestBuilderSelect browser and mode
host(String)TestBuilderOverride embedded server host
contentType(String ext, String mime)TestBuilderExtension → MIME mapping for static serving
webResource(String packageName [, contextPath [, url]])TestBuilderServe resources from a classpath package
webResource(WebApp, String url)TestBuilderServe resources from a WebApp
webResource(WebResource)TestBuilderServe a pre-built WebResource
webService(WebService)TestBuilderAdd a service endpoint to the embedded server
webSocket(WebSocket)TestBuilderAdd a WebSocket endpoint to the embedded server
Bootstrap steps
SignatureReturnsDescription
startJavascript([ScriptOption])TestBuilderStep: serve a minimal HTML fixture and navigate to it
startYojaWeb([ScriptOption])TestBuilderStep: serve a full Yoja-Web fixture page and navigate to it. The page loads YojaWeb.js with yw-config-path="/YojaWeb.conf.js" — serve that path to configure yoja-web in tests
getPage(String path)TestBuilderStep: navigate to the given path on the embedded server
Test steps
SignatureReturnsDescription
test(String name, Consumer<TestContext>)TestBuilderAdd a custom named step
testJsUnit(String path, List<String> functions, args...)TestBuilderRun named JS exports as individual test steps
testModule(String path, args...)TestBuilderRun a synchronous JS module as a test step
testAsyncModule([Duration,] String path, args...)TestBuilderRun an async JS module; optional timeout
loadModule(String path)TestBuilderStep: load a JS module without asserting on its result
repeatTestModuleUntil(Duration, String path, args...)TestBuilderStep: retry the module until it returns truthy or the timeout elapses
Utility steps
SignatureReturnsDescription
reload([ScriptOption])TestBuilderStep: reload the current page
resizeWindow(int w, int h)TestBuilderStep: resize the browser window
await(Duration)TestBuilderStep: pause execution
loadYwAssert()TestBuilderStep: inject ywAssert.js
saveLogs()TestBuilderStep: persist browser logs to disk
printLogs()TestBuilderStep: print browser logs to stdout
debugger()TestBuilderStep: IDE breakpoint hook
Run
SignatureReturnsDescription
execute([boolean keepRunning])voidRun all steps sequentially; optionally keep the browser open after completion
stream()Stream<DynamicNode>Expose steps as JUnit 6 @TestFactory dynamic nodes

ScriptOption class

Controls which helpers are injected after page navigation.

SignatureReturnsDescription
static apply()ScriptOptionFresh option with all flags off
saveLogs()ScriptOptionInstall the ywLogger.js log capture helper
loadYwAssert()ScriptOptionInstall the ywAssert.js assertion helper

📂 Runnable examples — full working tests in yoja-blueprint-kanban:

Browse them all under src/test/java/yoja/blueprint/kanban.

Test context

TestContext class (AutoCloseable)

Per-test handle injected into every Consumer<TestContext> step. Closing it shuts down both the embedded server and the WebDriver.

SignatureReturnsDescription
browser()BrowserActive browser enum value
seleniumService()SeleniumServiceThe wrapped WebDriver instance
httpServerContext()HttpServerContextEmbedded HTTP server details
httpUrlBuilder()HttpUrlBuilderURL builder pre-configured for the embedded server
logs()List<Log>Browser-side log entries
getHttpPage(HttpUrl | String [, Duration])voidNavigate the browser; optional explicit timeout
close()voidStop the embedded server and quit the WebDriver

HttpServerContext · HttpUrlBuilder classes

Embedded Yoja HTTP server serving test fixtures. Port auto-assigned from a monotonically-increasing sequence starting at 8888.

HttpServerContext
SignatureReturnsDescription
host()StringEmbedded server hostname
port()intAuto-assigned port
httpServerState()StateServer lifecycle state
httpUrlBuilder()HttpUrlBuilderURL builder pre-wired for this server
close()voidStop the embedded server
HttpUrlBuilder
SignatureReturnsDescription
path(String | Path)HttpUrlBuilder
query(String | HttpParameter)HttpUrlBuilderQuery string
fragment(String)HttpUrlBuilderURL hash fragment
build()HttpUrlBuild the full URL pointing at the embedded server

ywAssert

ywAssert browser-side assertions

ywAssert.js injects a small JUnit-style assertion library into the page as the global window.ywAssert. Call it from JS test modules run via testModule / testAsyncModule / testJsUnit — every failed assertion throws a JavaScript Error that Selenium propagates back to Java and surfaces as a failing test step.

It is not injected by default. Make it available three equivalent ways:

// 1. as a script option when starting the page
builder.startYojaWeb(ScriptOption.apply().loadYwAssert());
// 2. as a dedicated TestBuilder step (before any test that needs it)
builder.loadYwAssert();
// 3. on demand from inside a test step
builder.test("...", ctx -> ctx.seleniumService().loadYwAssert());
API
MethodThrows on failure
fail(error)Always throws Error(error)
assertEquals(expected, actual, message?)Throws unless JSON.stringify(expected) === JSON.stringify(actual) — deep equality for primitives, plain objects and arrays
assertArrayEquals(expected, actual, message?)Both must be arrays of equal length; each element compared via JSON.stringify; the error names the failing index and dumps both arrays
assertTrue(value, message?)Throws unless value === true (strict identity, not truthiness)
assertFalse(value, message?)Throws unless value === false (strict)
assertNull(value, message?)Throws unless value === null
assertUndefined(value, message?)Throws unless value === undefined
assertNotNull(value, message?)Throws if value === null
assertNotUndefined(value, message?)Throws if value === undefined

The optional message is prepended to the error with " -> " for context.

Behavior notes
NoteDetail
Strict equality onlyassertTrue / assertFalse use ===, not truthiness — assertTrue(1) fails; only the literal true passes. Same for the null / undefined checks.
JSON.stringify deep equalityObject key order matters ({a:1,b:2}{b:2,a:1}). Values that don't survive JSON (undefined, functions, Date, Map, Set, Symbol) are compared as their JSON form — convert them yourself first if precision matters.
Failure propagationEach failure throws a plain Error. From an async module, catch it and forward via reject(error.message) so Selenium lifts it into a Java SeleniumException on the right step.
Example — async test module
// /test/checkUserList.js
export default async function(args, resolve, reject) {
    try {
        const res   = await fetch('/api/users')
        const users = await res.json()

        ywAssert.assertEquals(200, res.status, 'wrong HTTP status')
        ywAssert.assertTrue(Array.isArray(users), 'response should be an array')
        ywAssert.assertArrayEquals(
            [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }],
            users,
            'user list payload')
        resolve()
    }
    catch (e) {
        reject(e.message)
    }
}

Logs

Log · Log.Level classes

Browser-side log entry collected by ywLogger.js and surfaced via SeleniumService.logs().

Log accessors
SignatureReturnsDescription
date()InstantTime the log entry was captured
level()Level
message()String
Log.Level values
ValueDescription
LOG · TRACE · DEBUG · INFO · WRAN · ERRORMirror the browser-side console levels; WRAN is intentional (matches the JS side)

DateUtil · Debugger · SeleniumException helpers

ClassKey APIDescription
DateUtilformatTimestamp(Long | Instant)Format a timestamp as yyyy-MM-dd HH:mm:ss in the JVM timezone
Debuggerdebugger()IDE breakpoint hook — throws and immediately catches a Debugger exception
SeleniumExceptionRuntime exception for WebDriver, script and embedded-server failures

Module README: yoja-selenium/README.md.