Software testing and QA engineering

Automated tests that fail for real reasons, run inside your pipeline, and give the team enough confidence to deploy on a Friday. Plus the exploratory testing that automation will never replace.

Who this is for

  • Your suite is green and production still breaks in ways nobody predicted.
  • You have thousands of tests, a stubborn subset fail at random, and the team has learnt to hit re-run until the build passes.
  • You inherited a codebase with no tests, so every change is a gamble and nobody refactors anything.
  • An enterprise customer's questionnaire asks about load testing, accessibility conformance and vulnerability scanning, and you have no evidence to attach.

What usually goes wrong

The problems this work exists to solve

The test pyramid is upside down

Teams that got burned by unit tests missing integration bugs often over-correct into a huge browser-driven suite. It takes 40 minutes, breaks when a class name changes, and still cannot tell you which layer failed. We measure the current distribution and the per-test runtime from your CI history, then push assertions down: business rules become unit tests, anything that touches the database becomes an integration test against a real Postgres in a container, and the browser suite shrinks to the journeys that cost money if they break. The pyramid is not a quota — it is a statement about how fast a failure tells you where the bug is.

Flaky tests have been normalised

Once a suite fails intermittently, a red build stops meaning anything and the gate is dead even if it is still configured. We instrument reruns so every test gets a measured flake rate rather than a reputation, quarantine the worst offenders so the build goes trustworthy again immediately, then fix by cause: sleeps instead of waiting on state, shared seeded data with order dependence, unfrozen clocks and Asia/Kolkata versus UTC assumptions, unseeded randomness, real network calls to third parties, tests that leak state through a shared cache. Blanket retries are not a fix — a retry that hides a race condition is a race condition shipped to production.

Coverage became a target, so it stopped being a signal

A mandated 80% line coverage produces tests that execute code and assert nothing, and snapshot tests that get re-recorded whenever they fail. Coverage tells you what ran, not what was verified. We use it two ways only: coverage on the diff, so new code arrives with tests without demanding a retrospective push across the whole repo, and mutation testing on the modules where a defect is expensive — Stryker, PIT or mutmut deliberately break the code and check whether any test notices. A module at 90% coverage that survives most mutants is telling you something a percentage never will.

The code that matters most has no tests and resists being tested

Legacy code usually has no seams: constructors that open connections, static singletons, business logic inside controllers, side effects halfway down a call stack. Writing unit tests first means rewriting first, which is how these projects stall. We start with characterisation tests at the widest boundary we can reach — HTTP in, database and files out — pinning current behaviour including the bugs, using golden-master comparison where the output is large. That safety net makes it possible to extract seams incrementally, at which point real unit tests become cheap. We pick starting modules from git history: the files that change often and appear in incident reports.

Performance testing happens the week before launch

A last-minute load test tells you whether today's build survives a guess at your traffic. It cannot tell you which commit made things worse. We build a workload model from real traffic — endpoint mix, read/write ratio, payload sizes, cache hit rate, arrival pattern rather than flat concurrency — and script it in k6 or Gatling against an environment sized like production, with the same data volume, because a table with ten thousand rows and one with ten million produce different query plans. Then a short smoke load run joins the release pipeline with thresholds on p95 and p99, so a regression gets attributed to a change instead of discovered by customers.

Accessibility is checked with a scanner and declared done

Automated tooling catches a minority of WCAG failures — mostly contrast, missing labels and landmark structure. It cannot tell you that focus jumps to the top of the page after a modal closes, that a custom dropdown is unreachable by keyboard, that a form error is announced nowhere, or that a live region shouts on every keystroke. We run axe-core inside the component and end-to-end suites so the mechanical failures never regress, then audit manually against WCAG 2.2 AA with keyboard only and with NVDA and VoiceOver. Findings come back as the specific success criterion, the failing element and a suggested fix, not as a scanner export.

Scope

What you actually get

  • A written test strategy

    Which risks are covered at which level, what is deliberately not automated and why, who owns which suite, and what each pipeline stage is allowed to block. Short enough that developers read it, specific enough that a test written at the wrong level gets pushed back in review.

  • An automated regression suite

    Framework set up in your repository with fixtures, data builders, parallel execution and deterministic setup and teardown. Tests are written against stable selectors and application state, not against markup, so a CSS refactor does not produce a hundred failures.

  • Pipeline integration and gates

    Staged runs: lint and unit on every push, integration on merge, full regression nightly. Dependency and container-layer caching, sharding across runners, diff coverage reporting on the pull request, and traces, videos and request logs uploaded automatically on failure so nobody has to reproduce locally.

  • Load and soak test scripts with a baseline

    Scripted workload model, a recorded baseline you can compare against later, and a written capacity finding: what broke first as load ramped, at what point, and what to fix or scale. The scripts stay in your repository and run on demand.

  • Security checks inside the pipeline

    SAST, dependency and container scanning, secret scanning, and an authenticated DAST run against staging. Tuned so that only findings which genuinely block get to fail a build — an untuned scanner is ignored within a fortnight.

  • Accessibility audit and automated guards

    Manual WCAG 2.2 AA audit with keyboard and screen reader, a prioritised findings list mapped to criteria, and axe checks wired into the suite so fixed issues stay fixed.

  • Exploratory testing and handover

    Session-based exploratory charters on new features, with reproducible bug reports. At the end of an engagement your developers get a walkthrough of the suite, the flake process and the gates, because a test suite nobody on the team understands decays fast.

Technology

Technology options

We are technology-agnostic. These are the choices we reach for, and how the decision actually gets made.

Browser and end-to-end

PlaywrightCypressSelenium WebDriveraxe-core

Playwright is the default: auto-waiting removes a whole class of flakiness, the trace viewer makes CI failures debuggable without reproducing them, and parallel workers are cheap. Cypress stays if your team already knows it well and the suite needs neither multiple tabs nor heavy parallelism — cross-origin stopped being the constraint once cy.origin landed. Selenium only when a client mandates an existing grid or an unusual browser matrix — the suite will need more explicit wait discipline in return.

Unit and integration

VitestJestpytestJUnitTestcontainersGo testingWireMockMSW

Integration tests run against a real database, cache and broker in Docker rather than mocks, because the expensive bugs live in transaction isolation, constraint violations, migration ordering and query plans — things a mock cannot reproduce. Third-party HTTP gets stubbed at the network edge against recorded contracts. The trade-off is slower runs and a Docker requirement in CI, which we buy back with parallelism.

API and contract testing

PactOpenAPI + SchemathesisREST AssuredsupertestNewman

For services owned by different teams, consumer-driven contracts let each side deploy independently instead of coordinating a full-stack staging soak. Schema-driven fuzzing suits public APIs where you care about malformed input more than about a specific consumer. Contracts need an owner and a review rule, otherwise they drift and become a second source of truth nobody trusts.

Mobile

MaestroDetoxAppiumXCUITestEspresso

Maestro is fast to write and stable enough for React Native journeys. Appium when one suite must cover both platforms and run on a device cloud for fragmentation coverage. Native XCUITest and Espresso when suite stability matters more than sharing code across platforms — they are more work per test but far less prone to driver-level flakiness.

Performance and quality gates

k6GatlingLocustJMeterStrykerPITSemgrepTrivyOWASP ZAP

k6 is the default load tool: scripted in JavaScript, thresholds that fail a pipeline stage, and output that correlates cleanly with traces. JMeter when your ops team already runs it and the handover matters more than the tooling. Mutation testing is applied to selected modules rather than the whole repository, because it is slow and only earns its runtime where a missed defect is costly.

How it runs

The shape of the engagement

  1. 01

    Audit the suite and the build history

    We run what you have, time every test, and pull the last several weeks of CI results to see which tests actually fail, how often, and whether the failure was real. In parallel we list the critical paths that have no coverage at all. The output is a list ranked by risk and by minutes wasted, not by coverage gap.

  2. 02

    Agree the risk model with you

    A duplicate payment, a patient record shown to the wrong clinician and a misaligned button are not the same category of failure. We write down which failures are unacceptable, which are merely annoying, and which paths carry money or personal data. That ranking decides where automation spend goes and what is left to exploratory testing.

  3. 03

    Stabilise before adding anything

    Adding tests to a suite nobody trusts adds noise. We fix or quarantine the flakiest tests, cut runtime with caching and sharding, and get a red build to mean something again. Only then is it worth making that build a required check.

  4. 04

    Build one vertical slice properly

    One critical journey — signup, checkout, claim submission — implemented all the way through: data setup, assertions, parallel-safe isolation, pipeline wiring, artefacts on failure. Reviewing that slice with your developers settles the conventions before we scale out, rather than after two hundred tests have baked in a bad pattern.

  5. 05

    Wire the gates and the escape hatch

    Required checks, diff coverage threshold, load smoke run, accessibility check. Just as important: a written rule for when a gate may be bypassed and who signs it off. Gates without a legitimate override get bypassed illegitimately.

  6. 06

    Hand over and review the first failures

    Your team takes ownership with documentation and a walkthrough. We stay close for the first weeks of real failures, because the way a team responds to its first few red builds decides whether the suite survives.

Engineering

Architecture, security and performance

The decisions that are expensive to change later, and where we stand on them.

Architecture

Test level is chosen by the failure it can catch
Before writing a test we name the failure it is meant to detect. If that failure is a wrong calculation, it belongs in a unit test where it will point at a function. If it is a broken foreign key or a migration ordering problem, it belongs in an integration test against a real database. If it only appears when three services and a payment provider interact, it belongs in a small end-to-end suite. Written down, this rule lets a reviewer reject a browser test that is really checking a pricing rule.
Real dependencies in containers, doubles only at the network edge
We run Postgres, Redis and Kafka as containers in the test run rather than mocking their clients. Mocks encode what you believe the dependency does; the container shows what it does, including isolation levels, unique constraint behaviour under concurrency and index-dependent query plans. Only third-party services outside your control get stubbed, against recorded responses that are re-verified periodically so the stub does not quietly diverge from the real API.
Test data is a design problem, not a fixture file
A shared seeded database is the single largest cause of flakiness and order dependence we find. Each test creates the data it needs through builders that go via the application's own code paths, so a schema change breaks the builder once instead of breaking fifty fixtures. Isolation comes from a transaction rolled back per test, or a schema per parallel worker where the code commits internally. This costs more setup code up front and buys safe parallelism, which is what keeps the suite inside its time budget.
Determinism is engineered, never hoped for
Time is injected rather than read from the system clock, so month-end and daylight-saving logic can be tested directly. Random seeds are fixed and printed on failure. Waits are on observable state, never on a duration. Feature flags are set explicitly per test rather than inherited from an environment. Every one of these is a small discipline, and together they are the difference between a suite that survives two years and one that gets deleted.
Suite runtime is a budget with a hard limit
A pull request check that takes more than roughly ten minutes gets worked around — people stack changes, merge on amber, or stop running it locally. We treat runtime as a design constraint: shard across runners, select tests by impacted area for pull requests, cache dependency and container layers, and move the exhaustive combinations to a nightly run. Slow suites are not thorough suites; they are unused suites.

Security

Authorisation is tested, not reviewed
Broken object-level authorisation is the defect class that most often reaches production, because it appears one endpoint at a time and code review has no memory. We build a matrix of role by resource by action and assert the negative cases in the API suite: user A cannot read, update or delete user B's record, a revoked token fails, a tenant identifier taken from the request body is ignored in favour of the session. New endpoints join the matrix, so the gap closes automatically instead of depending on someone remembering.
Scanners tuned to a policy, not switched on and ignored
SAST, dependency and container scanning and secret detection all run in the pipeline, but each finding class gets a decision in advance: known-exploited vulnerabilities and leaked secrets fail the build immediately; a transitive dependency advisory with no reachable call path opens a ticket. Without that policy the pipeline produces hundreds of findings in week one, everyone learns to skim past them, and the scanning is worse than useless because it looks like coverage.
Dynamic scanning with a real authenticated session
A DAST run that cannot log in only scans your login page. We give ZAP a scripted authentication flow and a session it can maintain, then add targeted checks that generic scanners miss: rate limiting on credential and OTP endpoints, mass assignment on update handlers, file upload content-type and size handling, and server-side request forgery on any endpoint that fetches a supplied URL.
Test environments never contain production data
Restoring a production dump into staging so the tests have realistic data is an unauthorised disclosure into a less-controlled environment — something you will have to assess, and potentially notify, under the DPDP Act or HIPAA. We generate synthetic data that preserves the shapes that break code — unicode and single-quote names, very long addresses, leap-year dates, boundary amounts, duplicate identifiers — and where realistic distribution genuinely matters, we mask irreversibly rather than pseudonymise reversibly.
The test pipeline is itself an attack surface
CI runners hold deployment credentials, and test tooling pulls a large dependency tree that nobody reviews. We pin action and image versions by digest, deny secrets to workflows triggered by forked pull requests, scope deployment tokens to the environment they deploy, and keep test dependencies in a separate lockfile that is scanned like production code.

Performance

A workload model, and percentiles rather than averages
Average latency hides the queue. We report p50, p95 and p99 with the error rate beside them, and drive load from a model of real traffic: the endpoint mix, read to write ratio, cache hit rate, payload size distribution and an arrival pattern that reflects how users actually turn up. Hammering one endpoint at fixed concurrency measures that endpoint, not your system, and reliably misses the contention that only appears when reads and writes compete.
Ramp until something breaks and record what broke first
A test that confirms the system handled expected traffic tells you nothing about margin. We push past the target until a limit is reached, then identify it precisely: database connection pool exhaustion, a thread pool queue, garbage collection pauses, a lock on a hot row, an upstream rate limit, or the load generator itself. That first bottleneck and the load at which it appeared is the only useful input to a capacity plan.
Instrument the system under test, not just the generator
Load tool output tells you it got slow. Traces tell you why. We run performance tests with tracing on and correlate a p99 spike back to a specific query, external call or lock, so the outcome is a fix rather than an observation. Without that correlation, teams end up scaling hardware to hide an N+1 query.
Soak and spike find different bugs
A soak test held for hours reveals memory leaks, unclosed connections, unbounded in-process caches and log volumes that fill a disk — none of which show up in a twenty-minute run. A spike test reveals cold caches, autoscaler lag, retry storms and the thundering herd when a popular cache key expires. We script them separately because the failures they cause need different fixes.
Front-end performance is measured on the device, not the office wifi
Where the product is a web application, we measure Largest Contentful Paint, Interaction to Next Paint and Cumulative Layout Shift under throttled CPU and network settings that match a mid-range Android phone on a mobile connection, and enforce a bundle size budget in the pipeline. A build that regresses the budget fails, which is far cheaper than discovering the regression in field data a month later.

Size of work

Where your project probably sits

A rough map so you can locate yourself before talking to us. Actual scope comes from a conversation, not a table.

LevelExampleEngagement shape
Focused taskOne critical journey automated end to end — signup, checkout or payment — wired into your existing pipeline with failure traces, or a one-off flake triage across a suite that has stopped being trusted.One QA engineer in your repository for days rather than weeks, finishing with the suite running in your CI and a walkthrough for your developers. From about Rs 25,000.
ProjectTest strategy, automation framework, staged pipeline, load-testing baseline and a WCAG audit for a single product.Several weeks. A QA engineer with part-time senior developer support, weekly demos run against the pipeline itself, and a handover session at the end so the suite has an owner on your side.
Embedded quality engineeringA QA engineer inside your squad owning suite health, test data, release gates, exploratory sessions each sprint and the load smoke run before every release.Monthly, working to your sprint cadence and your board. Scope and priorities reviewed each month; you can stop or resize at a month boundary.
PlatformShared test infrastructure across several teams: common fixtures and data builders, an ephemeral environment per pull request, contract testing between services, and a flake dashboard teams are accountable to.Multi-month with a small team including an infrastructure engineer, roadmap reviewed quarterly alongside your platform group.

Honest limits

When this is not what you need

Telling you this early is cheaper for both of us than discovering it in month two.

  • If the architecture is the real problem, tests will only document it in more detail. A service that cannot be started in isolation, or a database every module writes to directly, needs seams before automation is worth the spend — that is modernization work, and we would rather say so than sell you a suite that entrenches the design.
  • We do not supply testers by headcount. If what you want is a team clicking through a scripted checklist each release, someone will do that more cheaply than us, and you should let them.
  • We are not a penetration testing firm and hold no security certifications. We test authorisation logic and build scanning into your pipeline; a formal penetration test report for a customer, insurer or regulator should come from a licensed assessor.
  • If you are pre-product-market-fit and rewriting screens every week, a large end-to-end suite will slow you down for no benefit. A handful of smoke tests on the money path and nothing else is the correct answer at that stage, and we will tell you that rather than build the suite.

Questions

Common questions

  • Do you do manual testing as well, or only automation?

    Both, for different jobs. Automation is for regression — verifying that what worked yesterday still works, cheaply and repeatedly. Exploratory testing is for discovery, and it is better than automation at finding problems in a brand-new feature, at judging whether an error message is actually usable, and at noticing the thing nobody thought to specify. We run session-based exploratory charters with a stated mission and a written record, so it is reviewable work rather than unstructured clicking.

  • Will you insist on replacing our existing framework?

    No, unless the framework is the actual problem and we can show you why. Migrating a suite is expensive and mostly produces tests you already had. Far more often the issue is test design — shared data, brittle selectors, assertions at the wrong level — and that is fixable inside whatever you already run. If we do recommend a move, you will get the reasoning and the cost before anything is rewritten.

  • Where do you work from, and how does an engagement start?

    We are based in Ravet, Pune, and work with teams across Pune and PCMC, elsewhere in India, and internationally. Working hours are Monday to Friday, 10:00 to 19:00 IST, with overlap arranged for other time zones. The usual first step is an audit: read access to the repository and to a few weeks of build history, from which we come back with the flake list, the untested critical paths and a ranked plan of what to fix first.

Keep reading

Related work and reading

Related services

  • Software maintenance and support

    Once the suite is trustworthy, the same gates carry ongoing patching, dependency upgrades and bug-fix releases without a manual regression pass each time.

  • Software modernization

    If the code has no seams and cannot be tested without restructuring, the untangling work is described here — characterisation tests are where that project starts.

  • Cloud and DevOps

    Test suites need pipelines, container runners and ephemeral environments per pull request; that infrastructure work lives on this page.

  • API development

    Contract testing, schema-driven fuzzing and idempotency test cases are designed alongside the API rather than bolted on afterwards.

  • MVP development

    For an early product, the right amount of testing is small and deliberate — this explains what we do keep at that stage.

Technologies

Industries

Send us your build history

The fastest way to start is an audit. Give us read access to the repository and a few weeks of CI results, and we will come back with the flake list, the critical paths that nothing covers, and what to fix first. If the honest answer is that you need less testing than you think, we will say that too.