Skip to main content

Search...

End-to-End Testing: How to Stop the Maintenance Trap

Flaky end-to-end tests and 40% of time lost to maintenance: one HTML attribute added in 2017 still prevents this today.

11 min read
Cover of the expert talk on 'End-to-End Testing: How to Stop the Maintenance Trap' with Lilia and Richard Seidl.

End-to-end test automation at scale is the practice of building a stable, maintainable automated test suite for large, long-lived applications without accumulating flakiness and maintenance debt. It rests on three foundations: accurate UI element identification through dedicated HTML test attributes, a deliberate selection of a small number of high-coverage test cases, and a layered framework architecture that centralizes reusable logic and enforces consistent naming conventions.

Key Takeaways

  • A custom HTML attribute added solely for test purposes, introduced in 2017, still delivers stable, flakiness-resistant element identification years later across a large government application portfolio.
  • Scoping the DOM so that only the relevant section exists for the test tool removes ambiguity when identical elements appear in multiple sections, making precise targeting possible without complex selectors.
  • Fifteen well-chosen test cases covering the most critical scenarios deliver more sustainable value than five hundred loosely defined ones dragging down execution time and maintenance capacity.
  • Sleep-time workarounds accumulate silently: one analyzed project lost two hours per test run to individually small hardcoded waits, a cost eliminated only by building a proper application-busy detector.
  • Naming every automation procedure as action plus business label (for example, “enter name”) means a new team member can read the test code and navigate the application logic without separate documentation.

Test automation is software development, not a side task

Test automation is a software project inside the larger one. That framing changes how you build it: with architecture, naming conventions, design patterns, and the same discipline you apply to production code.

Lilia Gargouri spent fifteen years in software development before moving into quality engineering and automation at scale. From that vantage point, the shortcut mindset around automation is the root of most trouble. Generating test code quickly does not scale. It accelerates the mess.

The end-to-end layer sits at the peak of the test pyramid, and that is where flakiness lives. Teams routinely burn a large share of their automation time on flakiness and maintenance. The problems are old and familiar, yet they persist. Pouring AI on top without a strategy multiplies them faster.

Why enterprise applications punish weak automation

Long-lived enterprise software exposes every weakness in a test suite. An application that runs for twenty-five years, evolves through release cycles, and carries high business complexity means everything scales at once: the number of tests, the execution time, and the maintenance load.

Lilia builds automation for large e-government projects, where this pressure is constant. New features arrive while existing ones need maintenance, and the suite grows in every dimension. Without a clear strategy from the start, teams fall quickly into the maintenance trap and stay there.

The order of work matters. You do not start by generating tests. You start by solving recognition, then design, then prioritization, and only then implementation.

How to make the UI reliably testable

Reliable end-to-end automation begins with accurate element recognition, and that begins with the quality of the HTML. Fixing recognition after the fact is a temporary patch, not a solution.

Most automation worldwide still targets web UIs, which means the hard part is pointing at one tiny, specific element. Picture a table with fifty delete buttons, each a small icon. Targeting the exact one you want is where fragile locators fail.

The durable fix is a dedicated HTML attribute for testing only. Call it a data role attribute, a test ID, whatever fits your team. It carries a value used solely for test recognition, it is required across the whole application, and it never changes. If it is missing, that is a bug ticket for the developer.

This decision pays off for years. Lilia’s team introduced the attribute in 2017 and still reaps the benefit. Automation is a sum of tiny decisions that compound into efficiency.

The second piece is a central mapper. It maps each attribute value to a test automation class, so the tool recognizes a table, a row, a cell, an icon button. Keeping that mapping in one place means the implementation and its maintenance also live in one place, loaded the moment the browser opens.

Enrich identity through accessibility attributes

Accessibility attributes double as a source of meaning for test code. Because e-government applications must serve blind users, attributes like aria-label and alt are already present and already meaningful.

Lilia’s scripts extract that information to enrich the identity of UI elements. The side effect is real coverage: testing implicitly checks that accessibility values make sense to the people who rely on them. Code that reads like the interface it drives is easier to understand later.

Shrink the scope to sharpen targeting

Narrowing the DOM to a single section makes precise targeting possible. When a field name repeats across many sections, you cannot reliably hit the fifth one inside a full page.

The technique is to make the rest of the DOM disappear for the tool, leaving only the relevant section. With a small scope, you target accurately, runs become stable, and reruns become trustworthy. Stability is the product of many such decisions combined.

Identify by label for stable, generic code

Identification by label keeps tests readable and, in this context, stable. Government applications rarely change the labels of input fields, so a locator like the text field with the label “name” holds up over time.

Labels also make multilanguage support cheap. Put the label translations in a spreadsheet, reference the label generically, and the same line of code runs against English, Italian, or any other language depending on the login. One ID, one line, no duplication.

Design a small set of high-value test cases

Fewer, sharper end-to-end tests beat a large redundant suite. Lilia is not a fan of five hundred test cases. Fifteen that cover the most important behavior are enough to start a serious conversation about quality: if they are green, you can talk about the rest.

Think of each test case as a stone in a backpack you carry for years. If the pack is heavy, the journey suffers. So you decide, deliberately, what earns a place.

Three pillars decide the quality of a test case.

PillarQuestion to ask
ScenarioWhat is the focus, and is it critical? Can a step be removed without losing coverage?
Test dataWhich boundaries and equivalence classes carry real value, and are the data reusable and maintainable?
ImplementationDoes the implementation keep maintenance low, whether manual or automated?

Squeeze each test case down to its core. Do not blindly pull cases from the test management tool and automate them with their redundancy intact. Clarify the focus, cut steps that add nothing, and choose test data that actually covers something instead of “ABC123” everywhere. Expensive automation with meaningless data delivers no value.

Prioritize the backlog before you automate anything

A defined backlog is not a signal to start coding. It is the point where you choose an order that fits your situation.

Priorities depend on strategy and context. You might automate the business-critical cases first, so a green pipeline gives you confidence where it counts. You might target the cases that take manual testers the longest, freeing them for new features. You might cover the happy paths or the most-used flows first. The right sequence depends on the here and now, not on a fixed rule.

A three-layer framework keeps automation maintainable

A layered architecture is what lets automation survive years of change. The structure mirrors the ISTQB advanced automation model: core libraries at the bottom, business logic in the middle, test scripts on top.

Test cases and suites sit on top and call procedures that handle the business. The business logic calls the libraries. Each layer has one job.

The libraries themselves are layered. The tool ships a standard library, which is never enough. Above it sits a cross-project company library for functionality like switching browser windows, so no project reinvents it. Above that sits a project library for repeated interactions, such as a context menu behind three dots: one procedure takes the action you want and performs it. Specialized libraries handle email content and PDF content. Every time you find yourself building something new, put it in the right library.

A naming convention is what guarantees clean code

Consistent naming is the mechanism that keeps a growing suite readable. The specific convention matters less than using it everywhere, all the time.

Lilia’s convention is action plus business. The action is enter, check, or click. The business is the label. So “enter name” means putting a value into the field labeled name. The technical widget type stays out of the wording: a checkbox, a radio button, a date picker are all “enter” plus their label. If you close your eyes, the code lets you navigate the application by its business.

Readable code shortens onboarding sharply. New team members who join Lilia’s team can spend weeks on a single test case, on purpose, until they absorb the convention and apply it everywhere. The structure of the automation matches the structure of the UI, so anyone who can navigate the interface can navigate the code.

There is a diagnostic hidden in this. If you catch yourself searching for where something was implemented, your classification is not good enough.

If you tap yourself searching for something, where did we implement this and where, it means your system is not good enough. — Lilia Gargouri

Why maintenance stays low: one change, one place

Centralization and generic code keep the maintenance cost of change small. When something changes, a well-structured suite ideally has one place to fix.

Contrast that with concatenated IDs and raw XPath. When such a line fails, you first split it to find which part broke, then decide whether it is a bug or a feature, and you repeat that every single time the line fails. Under release pressure, that is where teams lose their days.

The failure mode to fear is scale without structure. Search for an ID and find five hundred matches, then face a release candidate where that element changed as a feature, and you must repair five hundred findings fast. That is a ticking bomb.

How to stabilize execution and cut wasted time

Stable execution comes from waiting on the right signal, not on the clock. Sleep times accumulate silently and solve nothing at the root.

Use an application busy detector instead of sleeps. Built on top of the same resolver, it waits while the spinner is visible and continues when the application is ready. In one project across forty test suites, the sleep time alone added up to two hours per run: two milliseconds here, two milliseconds there, lost for nothing.

Keep the environment current, because versions interact. The browser, Java, Gradle, the system under test, and the test tool all shift over time and must work together to stay stable.

Isolate the run. No other jobs should run in parallel on the machine or container, or you inherit side effects, performance noise, and failures that are not yours. Make sure no other process touches the same data your tests use, or you get exceptions that send you chasing phantom bugs.

Modular test cases give you flexibility under pressure

Modularity turns a rigid suite into something you can recompose on demand. Each test case is an independent unit that does not care what runs before or after it.

That independence lets you assemble runs from an impact analysis: pick the topics you need and run them together. Test suites are separated by business topic, and when one grows past your time limit, one hour in Lilia’s case, you split it into subtopics without friction. You can then sequence smoke, integration, and functional runs and stop early when the critical layer fails.

The payoff shows up in awkward moments. When a tool update broke the Electron variant of a business application, the same test code still ran against the web version. Only the precondition changed, opening a browser instead of the Electron app. Within five minutes the web tests delivered functional results while the Electron problem was fixed, and the release was not blocked.

How to rescue an old, flaky suite

Rescuing a legacy suite starts with a parallel repository, not a rewrite in place. Keep the old world running while you build the new one beside it, and switch over fully at one point.

Set up the clean structure first, from a project template, then implement new test cases inside it with the naming convention. When you need procedures and libraries, harvest the valuable pieces from the old suite one by one and rebuild them as clean code in the new library. Lilia took over exactly this situation as a developer and grew the new solution out of the old mess piece by piece.

The migration takes time, and it is still worth starting. A ten-kilometer walk begins with one step, and the alternative is another day of flakiness and a large slice of your working hours lost to maintenance. Lower maintenance is, in the end, a matter of quality of life.

Share this page

Related Posts