Case study, IriusRisk

Testing a rules engine where every combination is a new case

At IriusRisk I automated end-to-end tests with Playwright and TypeScript for a threat-modeling product whose rules depend on questionnaires, components, trust zones, data flows and custom fields. Copying a flow for every combination would have produced a huge, fragile suite, so the work was mostly about structure: state, data and design that scale.

Product
Threat-modeling SaaS, used by international teams
Role
QA Engineer, May 2024 to June 2026, remote
Tested with
Playwright, TypeScript, REST Assured, JUnit 5
Result
About 80% less run time after the TestCafe to Playwright migration I worked on

The product

IriusRisk is a threat-modeling SaaS. Teams describe a system with questionnaires, components, trust zones and data flows, and a rules engine turns that model into threats and countermeasures. A single answer can change what the engine concludes several steps later.

Complex systems like this fail in combinations, state transitions and edge cases long before they fail in the happy path. These are the problems that mattered most in its end-to-end suite, and how I approached each one.

Tech: Playwright, TypeScript, REST Assured, JUnit 5 and the IriusRisk API.

A rules engine with a combinatorial space

A rule could depend on questionnaire answers, components, trust zones, data flows, security classifications or custom fields, and trigger several actions at once. Covering it by copying a flow per combination would have produced a huge, fragile suite.

How I approached it

  • Page Objects and shared helpers for the complex screens
  • Entities created through the API when the UI added nothing to the test
  • Unique data per run, so runs never collide

Questionnaires that drive business rules

An answer could show a question, insert another answer or produce a conclusion that fired a further rule. Each change had to be checked against the state before it, including what must not happen.

How I approached it

  • Controlled starting states for every scenario
  • Setup kept apart from rule execution, so a failure points at its cause
  • Negative cases where a rule has to stay silent

Data that breaks where clean data doesn’t

UTF-8 text and double quotes in questions and answers could break saving or rule evaluation, and none of it showed up with tidy test data.

How I approached it

  • Edge-case values in the suite on purpose
  • Round-trip checks from the UI through the API to storage and back

A flaky suite made reliable

Elements existed in the DOM before they were ready, selectors matched more than one element, and positional selectors could pass on the wrong one.

How I approached it

  • Semantic locators: getByRole, getByLabel and filters by context
  • Waits on observable state instead of fixed sleeps
  • Failures clear enough to tell a test problem from a product bug

Dynamic forms across UI, API and data

Custom Fields and Security Content mixed dynamic forms, validations and references between entities, and backend errors surfaced in the UI.

How I approached it

  • Checks on messages, state and persistence, not only on the click
  • Positive and negative cases for every constraint
  • Preconditions created through API helpers

Test architecture that scales

Every new condition or action threatened to mean another copied flow.

How I approached it

  • Page Objects, API helpers and data factories
  • Parameterized scenarios for variants that share setup and assertions
  • Reuse without hiding what each test checks

The pipeline that ran it

The suite ran in 7 CI containers, one per product module. A Python optimization model showed that 4 containers would be about 8% faster than 7 and cost 41% less per run.

Contact

I'm Pedro Morago, a Senior QA Engineer with a Mathematics degree and 10 years of poker, working remotely from Spain. If you'd like to talk about testing complex, rule-driven products, email me at pedro@pedromorago.com.