Create a new playwright module that provides a Kotlin DSL for Playwright browser automation. The module is a consumer of the existing @GeneratedDsl annotation framework (no custom KSP processors). It uses auto-managed lifecycle (browser launch/close handled automatically). Initial scope covers core functionality (browser launch, context, page navigation, locators, screenshots).
playwright/
build.gradle.kts
src/main/kotlin/org/khorum/oss/euri/playwright/
PlaywrightVersion.kt
config/
ProxyConfig.kt
ViewportConfig.kt
GeolocationConfig.kt
HttpCredentialsConfig.kt
ScreenSizeConfig.kt
VideoSizeConfig.kt
ClipConfig.kt
BrowserLaunchConfig.kt
BrowserContextConfig.kt
NavigationConfig.kt
ScreenshotConfig.kt
LocatorOptionsConfig.kt
LocatorFilterConfig.kt
runtime/
PlaywrightDsl.kt -- top-level entry, auto-managed lifecycle
BrowserScope.kt -- browser operations
BrowserContextScope.kt -- context operations
PageScope.kt -- page interactions
LocatorScope.kt -- locator chaining + actions
FrameScope.kt -- iframe handling
mapping/
ConfigMappers.kt -- config.toPlaywright() extension functions
- Add
playwrightversion togradle/libs.versions.toml - Add
"playwright"toincludeModules(...)insettings.gradle.kts - Create
playwright/build.gradle.ktsmatching thedslmodule's KSP configuration but as a consumer (depends on:dsl, adds Playwright dependency)
Create @GeneratedDsl data classes for reusable configs:
ProxyConfig(server, bypass, username, password)ViewportConfig(width, height with defaults 1280x720)GeolocationConfig(latitude, longitude, accuracy)HttpCredentialsConfig(username, password, origin, sendImmediately)ScreenSizeConfig,VideoSizeConfig(width, height)ClipConfig(x, y, width, height)
BrowserLaunchConfig- headless, channel, args, proxy, slowMo, timeout, etc.BrowserContextConfig- baseURL, viewport, locale, geolocation, permissions, httpCredentials, extraHTTPHeaders, userAgent, colorScheme, etc.
NavigationConfig(url, waitUntil, timeout, referer)ScreenshotConfig(path, type, quality, fullPage, clip, omitBackground)LocatorOptionsConfig(hasText, hasNotText)LocatorFilterConfig(hasText, hasNotText)
Create mapping/ConfigMappers.kt with extension functions:
BrowserLaunchConfig.toPlaywright() -> BrowserType.LaunchOptionsBrowserContextConfig.toPlaywright() -> Browser.NewContextOptionsNavigationConfig.toPlaywright() -> Page.NavigateOptionsScreenshotConfig.toPlaywright() -> Page.ScreenshotOptionsProxyConfig.toPlaywright() -> ProxyViewportConfig.toPlaywright() -> ViewportSize(and other shared configs)
PlaywrightDsl.kt- top-level entry.playwright { chromium(); launch { headless = true }; page { ... } }. Auto-creates Playwright instance, launches browser, creates context/page, executes actions, cleans up.PageScope.kt- wrapsPage. navigate(), locator(), getByRole/Text/Label/TestId/Placeholder/AltText/Title(), screenshot(), content(), title(), url(), evaluate(), waitForSelector/LoadState/URL(), frame(), keyboard/mouse access,rawescape hatch.LocatorScope.kt- wrapsLocator. click(), fill(), press(), check/uncheck(), hover(), focus(), clear(), selectOption(). Chaining: locator(), filter(), first/last/nth(), getBy*(). State: isVisible/Hidden/Enabled/Disabled/Checked(). Content: innerText/innerHTML/inputValue/getAttribute/textContent().rawescape hatch.BrowserContextScope.kt- wrapsBrowserContext. addCookies(), clearCookies(), grantPermissions(), setGeolocation(), setOffline(), newPage(), storageState(),rawescape hatch.FrameScope.kt- wrapsFrame. Subset of PageScope: locator(), getBy*(), evaluate(), content(), url(), name().
PlaywrightVersion.ktwithTARGET_VERSION,MIN_VERSION, andcheckCompatibility()method- Gradle dependency constraint:
strictly("[1.50.0, 1.58.0]")
- Run
./gradlew :playwright:buildto confirm KSP generation works and code compiles - Fix any issues
- All
@EuriDsl-annotated scope classes prevent scope leakage (can't callpage {}from insidelocator {}) - Every scope exposes
val raw: Tfor direct Playwright API access when the DSL doesn't cover something - String enums (e.g.,
"LOAD","PNG") are mapped to Playwright enums in the mapper layer, keeping config classes simple - Auto-managed lifecycle: the
playwright { }block handlesPlaywright.create(),browser.launch(),browser.newContext(),context.newPage(), and closes everything in reverse order via try/finally
playwright {
chromium()
launch { headless = true }
context {
viewport { width = 1920; height = 1080 }
locale = "en-US"
}
page {
navigate("https://example.com")
getByRole(AriaRole.BUTTON, name = "Submit").click()
screenshot { path = "result.png"; fullPage = true }
}
}