This blog and concept was a collaboration between Leonardo Lanni & David Ingraham.
Most teams don’t build features.
They wait on them.
Development waits for product.
Then front-end waits for back-end.
Meanwhile QA waits for front-end and back-end.
And finally, testing gets squeezed at the end.
By the time everything connects, bugs show up when context is already gone. Does this sound familiar?
What if you never had to wait in the first place?
This blog introduces a different approach: treating the UI as a contract so everyone can move in parallel from day one. This means instead of work flowing in sequence, everything moves at once. Instead of testing being squeezed at the end, it starts immediately. And instead of integration being discovery, it becomes validation.
Prerequisites for Success
This approach is powerful, but it’s not something you can drop into any team overnight. If your team struggles with communication, this will expose it. If ownership is unclear, this will amplify it. If your process depends on clean handoffs, this will break that expectation entirely.
Because this model replaces handoffs with collaboration.
For it to work, a few things need to be true:
Strong cross-functional collaboration
Front-end, back-end, QA, and product stay aligned continuously, not sequentially.A shift-left mindset
Teams define behavior, write tests, and validate flows before everything is fully implemented.Shared ownership of quality
Testing isn’t owned by QA alone. Everyone contributes early.Comfort with partial implementations
Mocked data, incomplete UIs, and evolving designs are part of the process, not blockers.
Without these, things can get messy fast.
But with the right culture in place, this becomes a force multiplier for both speed and quality. With this in mind, let’s move on to how this is implemented in practice.
Core Idea
As soon as a screen exists, it becomes a contract.
A feature is typically composed of multiple screens. The moment one is defined, we establish how it should behave. This is the UI contract.
This contract can take different forms depending on the team:
- A design-driven contract (Figma designs, product requirements, defined behaviors)
- A code-driven contract (a UI skeleton with mocked data and interactions)
Both serve the same purpose: defining how the UI should behave before full implementation. Finding the right fit for your team is crucial in gaining buy-in.
In practice, we move quickly from design into a UI skeleton that becomes the executable version of that contract.
This isn’t a prototype or throwaway code. It’s the real UI, just powered by mocked data and behavior instead of live APIs.
This skeleton defines:
- All UI controls (inputs, buttons, tables, modals, etc.)
- Stable selectors (data-test ids, accessibility roles, labels)
- Navigation and validation rules
- Realistic mocked behavior instead of API calls
In addition to structure and interactions, the UI contract also defines how the application behaves under different scenarios.
And importantly, it defines how the system behaves under real-world conditions:
- Empty results
- Permission errors (e.g. 403 Forbidden)
- Validation failures (e.g. 422 with field-level errors)
- System errors
- Slow responses
In our implementation, these behaviors are formalized through structured error types, ensuring that both mocked and real providers expose consistent outcomes to the UI.
Throughout development, the mocked behavior is replaced with real API integrations, but the structure, selectors, and expected behavior remain the same.
How It Works
Once the contract exists, everything changes.
Instead of waiting, the team moves in parallel.
This approach is built around three core pieces:
- The UI skeleton (the contract itself)
- A mocked behavior layer
- Parallel work-streams across the team
1. UI Skeleton (Contract)
Early in development, we implement the UI structure itself:
- Page structure and routing
- Validation rules
- All system states (loading, empty, error, success)
- Basic layout and placeholders (UI controls, selectors, structure)
Again, how this definition looks can change between teams. Whether it’s a Figma design, detailed product ticket, real code, or a combination of these, this becomes the shared interface across front-end, back-end, and QA.
2. Mocked Behavior Layer
Rather than calling real services, the UI is powered by a mocked data layer (fixtures, MSW, dependency injection, etc.).
But these aren’t random mocks.
They simulate real-world scenarios like we alluded to earlier, such as successful or empty responses, validation errors, permission issues, or response latency.
Rather than exposing raw HTTP responses, providers return structured outcomes like:
forbiddenvalidation_errorsystem_error
This ensures the UI behaves consistently regardless of whether it is connected to mocked data or real back-end services.
The key is that the UI behaves as if it were connected to real systems.
As development progresses, we ensure mocked logic is replaced with real API integrations and the contract always stays stable.
3. Parallel Work-streams
With the contract in place, different roles can fully move independently without waiting on each other.
QA
- Ensures proper selectors exist
- Starts writing UI/E2E tests immediately
- Run the same tests later against real APIs and functioning UI without rewriting
Front-end Developers
- Build real logic incrementally
- Swap mocks for real services
Back-end Developers
- Define and implement APIs
- Use the UI and tests as a guide for expected behavior
Designers (or Product)
- Refine visuals as feedback arises
- Establish visual baselines once designs stabilize
Instead of handing work off between roles, everyone moves forward at the same time against a shared, stable interface.
UI-Level TDD (Test-Driven Development)
This approach enables a form of TDD at the UI level.
This is possible, again, by defining and validating UI behavior upfront:
- Define UI behavior and states
- Implement UI skeleton with mocks
- Write automated UI tests
- Replace mocked logic with real implementations
- Tests remain unchanged
The key benefit is that tests are written early and remain stable.
They validate behavior, not implementation details, so they continue to pass as the system evolves.
In practice, this is reinforced by running the same automated tests against both mocked and real implementations.
This dual-mode execution acts as a continuous validation that the UI contract holds across different environments and implementations.
Visual Regression Strategy
Visual testing can also be introduced in phases to match the maturity of the UI.
Early on, visual tests are optional or non-blocking and the focus is on behavior, not polish.
Later though, designs are finalized and approved. Visual baselines are then established and visual regression tests become blocking.
At that point, visuals become part of the contract too.
Real-World Example
This all sounds good in theory.
But what does it actually look like in practice?
Let’s pretend we need to build a new order form. Like most features, it starts with a detailed product ticket, business requirements, and a Figma design.
From there, we refine the feature across QA, front-end, back-end, and product. The goal isn’t just to scope the work, but to align on how the UI should behave, what edge cases exist, and what “done” actually looks like. This step is crucial because it ensures the entire team not only understands the upcoming feature, but has the opportunity to contribute to its design. No misunderstandings.
Once the feature is defined, we treat the design and product definition as the contract and start work in parallel. Throughout development, everyone communicates. Daily status updates and raising any additional questions or engineering concerns ensure no one drifts out of the loop.
Eventually, by the time the back-end endpoints are ready, the UI is mostly complete and tests are ~90% done. The last bit of work is just pulling in everyone’s changes and making the small adjustments needed to hook the entire flow up.
Bugs are caught early, feedback is fast, and there are no last-minute scrambles. By the time integration began, everything was already aligned.
What makes this different
At first glance, this can feel familiar — API mocking, shift-left testing, even prototyping. And in some ways, it builds on all of them.
But the difference is what everything is anchored to.
With API mocking, teams simulate backend behavior to unblock development. But those mocks are often temporary and loosely defined, drifting from reality over time.
Here, mocks are part of the contract itself. They don’t just return data, they define expected behaviors like validation errors, permission failures, and edge cases. Both mocked and real implementations are expected to behave the same way.
With shift-left testing, tests are written earlier — but they’re still often reacting to evolving systems and unstable interfaces.
In this approach, tests are written against a stable UI contract from the start. They don’t need to change as implementation evolves, because they validate behavior — not how that behavior is implemented.
And while prototypes are typically throwaway, the UI skeleton here is not. It starts early, supports real testing, and evolves directly into the final product. Nothing gets discarded.
The shift is subtle but important:
The UI isn’t just something we build and test.
It becomes the contract that everything else aligns to and that contract is executable from day one.
GitHub Demo Repository
To make this concept tangible, we built a small demo repository:
It showcases the approach end-to-end, starting with a UI skeleton that defines routes, controls, selectors, and states. This is paired with a provider abstraction that supports both mocked and real implementations, allowing the UI to behave consistently regardless of the data source.
The demo also includes a range of realistic scenarios — from happy paths to empty states, validation errors, permission issues, and even slow responses so behavior can be validated under real-world conditions, not just ideal ones.
On top of that, the same Playwright test suite runs against both mocked and real environments. This means testing can start early, remain stable throughout development, and continuously validate integrations without needing to rewrite tests.
Summary
By treating the UI as a contract, teams unlock a fundamentally different way of building software. Testing starts from day one, not the end. Build the contract first. Let everyone move against it. Replace mocks with reality over time.
This isn’t about making testing faster. It’s about removing the conditions that made you wait in the first place.
No bottlenecks.
No last-minute scrambles.
No rebuilding context when it matters most. Just continuous progress.
Eventually, you realize you were never blocked… you were just waiting.
From both of us, happy testing,
Leonardo Lanni & David Ingraham

Top comments (0)