惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
J
Java Code Geeks
P
Proofpoint News Feed
Recent Announcements
Recent Announcements
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
雷峰网
雷峰网
D
DataBreaches.Net
B
Blog RSS Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
V
Visual Studio Blog
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Martin Fowler
Martin Fowler
有赞技术团队
有赞技术团队
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I built a Kotlin DSL test automation framework because ex...
ane@agriviss · 2026-05-16 · via DEV Community

Tags: #testing #kotlin #opensource #cicd


Many test automation frameworks don't meet the engineering standards they're supposed to uphold.

I say that having used several professionally. Slow execution, brittle locators, silent failures, opaque logic, dead code, documentation that contradicts itself, CI pipelines held together with workarounds, and — my personal favourite — a framework from a reputable company whose generated selectors were so long they wouldn't fit on a widescreen. Its tooling produced 95% noise. Core functions silently failed or behaved unpredictably.

The cruel irony: frameworks built to enforce quality, failing their own quality bar.

So I built my own. This is QED.


What QED is

QED is a Kotlin DSL test automation framework for UI and API testing — open source, MIT licensed, and built from scratch on SOLID principles. The name comes from Quod Erat Demonstrandum — "that which was to be proven." Every test is a proof. Every run is evidence.

The framework takes that idea into the reporting layer:

  • ✅ Passed — Quod erat demonstrandum. Proven.
  • ⚠️ Skipped — Quaestio manet. The question remains.
  • ❌ Failed — Investigandum est. Further investigation is required.

It's built on top of tools you probably already know — Playwright, REST-assured, TestNG, and ExtentReports — but wraps them in expressive Kotlin DSLs that make tests read like intent rather than implementation.


The problems it solves

Brittle page objects

Most frameworks treat page objects as monoliths — tightly coupled, hard to reuse, duplicated across the suite. QED uses screen area composition: pages are assembled from smaller, independently testable components (header, sidebar, modal, etc.). If a shared component changes, you fix it once.

Glue code hell

Tools like Cucumber promise readable tests but often deliver fragmented logic split across step definitions, regex matchers, and external feature files. QED's DSL keeps the test logic in one place, expressed in the language of the domain.

Opaque CI pipelines

GitHub Actions configs have a way of growing into spaghetti. QED ships with a clean, documented pipeline structure — separate frontend and backend jobs, proper caching, and no redundant steps.


What the DSL looks like

Here's a mixed UI + API test — both in the same test context, sharing setup:

private val baseTest = BaseTest()
private val hasRest = HasRest(baseTest, urlKey = "apichallenges")
private val hasBrowser = HasBrowser(baseTest, urlKey = "uitestingurl")

class UI_RESTTest : TestContext(baseTest, hasBrowser, hasRest) {

    @Test(priority = 0, description = "mixed UI/API test", groups = ["All"])
    fun testUI_Rest() {
        val payroll = Payroll("create todo process payroll", true, "todo description")
        val json = QEDJson.toJson(payroll)
        val landingPage = GenericPage(UITestingPage(this))

        hasBrowser?.apply {
            navigateToApp()
            startFromPage(landingPage) {
                val result = rest.send(RequestType.POST, APIChalURLPath.SIM_ENTITIES, json, 201)
                verify("check response body") {
                    expect(result.get("name").asText()).to.equal("bob")
                    expect(result.get("id").asInt()).to.equal(11)
                }
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

The test reads top to bottom. There's no ceremony. The verify block is expressive enough that a non-engineer could follow the intent, but precise enough that an engineer can debug a failure without spelunking.


Real-world use: DairyMax

QED isn't a demo project. I use it to test DairyMax, a production web application I'm building. The framework runs in a GitHub Actions pipeline on every push — UI tests in one job, API tests in another, with proper dependency management between them.

That's meant one very practical constraint shaped the entire design: the framework has to work cleanly in CI, not just locally. Playwright browser setup, environment-specific SUT profiles, and test tagging (groups = ["All"], groups = ["Smoke"]) all came out of that real-world friction.


Architecture overview

qed-framework/
├── src/                   # Core framework
│   ├── base/              # BaseTest, TestContext
│   ├── browser/           # HasBrowser, Playwright wrappers
│   ├── rest/              # HasRest, REST-assured wrappers
│   ├── dsl/               # Verify, expect, page DSL
│   └── reporting/         # ExtentReports integration
├── QED-Shared/            # Shared utilities (submodule)
├── qed-demos/             # Example tests
└── .github/workflows/     # CI/CD pipelines

Enter fullscreen mode Exit fullscreen mode

The HasBrowser and HasRest capabilities are composed into a TestContext — you only include what a given test class needs. A pure API test never touches Playwright. A UI test that doesn't need REST doesn't carry that weight.


CI/CD with GitHub Actions

The pipeline is split into two jobs:

jobs:
  api-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
      - name: Run API tests
        run: ./gradlew test -Dgroups=API

  ui-tests:
    runs-on: ubuntu-latest
    needs: api-tests
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
      - name: Install Playwright browsers
        run: ./gradlew installPlaywright
      - name: Run UI tests
        run: ./gradlew test -Dgroups=UI

Enter fullscreen mode Exit fullscreen mode

UI tests run after API tests pass. Playwright browsers are installed as a dedicated step. Reports are archived as artifacts. Nothing clever, nothing fragile.


What's next

QED is intentionally lean. I want to keep it that way — the modular architecture makes it straightforward to extend without bloating the core. On the roadmap: deeper SUT profile management, richer assertion matchers, and longer term, some interesting ideas around AI-assisted test generation I'm exploring.


Try it

The docs cover prerequisites, DSL usage, page object composition, REST setup, CI/CD configuration, and the design philosophy in detail. Feedback, issues, and stars all welcome.

If you've built something similar or hit the same walls with existing frameworks — I'd genuinely like to hear about it in the comments.