Ritik Chaturvedi
Quality where the money is.
I own quality for the finance side of an ERP used by construction and manufacturing companies: accounts payable, payments, purchase orders, general ledger. Manual depth where it matters, Playwright automation where it repeats, and a root-cause write-up for every defect I raise.
QA Engineer at Merlin AI Software (YC S24), Bengaluru. Working toward backend engineering: I trace defects into the code and ship small, reviewed fixes with Claude Code.
I test the part of an ERP where mistakes cost money
Bills, payments, purchase orders, the general ledger: the screens an accountant uses every day, and where a wrong cent is a real problem, not a cosmetic one.
What I care about is why. A screenshot of something broken is the start of the job, not the end of it. I trace the symptom into the backend code, find what caused it, work out who else is affected by the same cause, and write that up so the fix is a decision rather than a guess. Increasingly I ship the fix too: small, well-scoped changes written with Claude Code, with the blast radius spelled out in the pull request and a developer's review before merge.
I automate the flows that repeat, in Python with Playwright, and I keep the automation honest: real clicks on real screens, no shortcuts through the API to make a test go green. A test that passes for the wrong reason is worse than no test.
I am working toward a backend engineering role, and I am doing it the long way round: by living inside the codebase I test.
How I got here, and where next
I am a tester first. The next stretch is about reading and writing backend code well enough to test it from the inside.
Diploma, Mechanical Engineering (Production) Done
Government Polytechnic Naraini.
B.Tech, Computer Science Engineering Done
Allenhouse Institute of Technology.
QA Engineer at Merlin AI Software (YC S24) Current
Quality for the finance side of a construction and manufacturing ERP, with root cause analysis and small reviewed fixes.
Public test framework Done
Playwright and pytest framework with an API layer, Postman collection and green CI. It is the suite running at the top of this page.
Daily Python practice In progress
Short exercises written from memory and committed to python-practice.
Backend basics and a job application tracker Planned
FastAPI, SQL with Postgres, and Docker, through one project: a small tracker API with its own test suite.
Data structures and an AI layer Planned
Problem-solving practice, then a small tool built on an AI API.
What I own at Merlin AI Software
QA Engineer, December 2025 to present. A construction and manufacturing ERP: Kotlin, Spring and MongoDB on the backend, Next.js and React on the front end.
Accounts payable
Bill creation and approval, approval reversal, paid and unpaid state, partial payments, recurring bills, credit notes, vendor AP and project AP rollups.
Payments
ACH, wire, cheque and card paths; bank confirmation numbers; future-dated payments; batch payment of several bills; bank-account and GL-account selection.
Receivables and deposits
Bulk deposits, manual deposits, GL, class and location coding, money not applied to an invoice.
Procurement
Purchase orders for materials and trade services, mixed POs, requisition-to-PO conversion, bids, receiving and shipment status, PO approval and void.
General ledger
Journal entries, CSV and payroll imports, import idempotency and retry, chart of accounts, bank reconciliation.
Integrations
QuickBooks sync: vendor and bill duplicate prevention, account mapping.
Access control
Granular role permissions across modules, user invitations and revocation.
Accounts payable and receivable, vendor and project payables, payments and batch payments, deposits, credit notes, recurring bills, purchase orders and requisitions, general ledger imports, and the QuickBooks sync.
I trace the symptom into the backend and front-end code, identify every other place the same cause reaches, and write the analysis into the ticket or the pull request so the fix can be scoped properly.
Small, well-scoped pull requests for the defects I find, written with Claude Code: backend service logic and front-end field mapping and error handling. Each one states its blast radius and the steps that prove it, and a developer reviews it before merge.
Python and Playwright, covering the critical money flows end to end, with a findings registry that records known defects so a known failure does not hide a new one.
When a customer's reported issues are fixed, I verify every one of them on staging in that customer's own workspace and publish a report the account team can take straight into a client call.
To get fixes in front of verification, including cherry-picking merged commits onto the staging branch when they have not reached it yet.
Customer contacts, the engineers who own each module, and the product lead, so a defect report turns into a decision instead of a queue entry.
Case studies
Seven defects and pieces of work, told the same way: what was reported, how I investigated, what the cause turned out to be, what changed. Customers are not named.
- Reported
- A customer's bill list and the bill's own page disagreed about the total by one cent. Small, easy to dismiss, and impossible to ignore in accounting: the two numbers have to match.
- Investigation
- Rather than hunt for it in customer data, I worked out the condition that produces it and then constructed it: a line amount plus a tax percentage whose product lands exactly on a half-cent in binary floating point. Then I went back to the customer's real bills and found the same condition occurring naturally.
- Cause
- Two different rounding helpers in the codebase. One multiplied by 100, rounded, and divided back, which breaks on a value that cannot be represented exactly in binary. The other used the platform's own decimal formatter with half-up rounding. The same amount passed through both routes and came out differently.
- Outcome
- The two routes were brought onto one rounding helper. I proved it on the constructed case, then re-proved it on the customer's real bills, which is the part that made it possible to sign off.
- Reported
- Approving a single recurring bill posted roughly three times the intended amount.
- Investigation
- I traced the schedule generation rather than the approval. The recurrence cursor began at the schedule's own start date, so by the time a bill was approved the schedule had already caught up, generating every period between the start date and today and posting them all at once.
- Cause
- A back-dated start date silently turns one bill into a backlog. Nothing in the flow treated the past as different from the next period.
- Outcome
- Raised with the money impact stated plainly and the reproduction steps attached, which is what got it prioritised rather than triaged into a backlog.
- Reported
- A purchase order containing both physical materials and trade services never reached its completed state, even once everything had arrived and every service was done.
- Investigation
- I read how the order header derives its status and found two separate rollups: one that counts received material lines, one that counts shipped service lines. Each rollup only recognised its own kind of line, so a mixed order satisfied neither.
- Cause
- A completion rule written for two pure cases, with no definition for an order that is both.
- Outcome
- I shipped the fix with Claude Code and a developer's review: completion now requires that all material lines are received and all service lines are shipped, with the case of no lines of that kind handled explicitly so pure orders behave exactly as before. The pull request stated the blast radius, since the rule governs every order's status.
- Context
- Two enterprise customers had long lists of reported issues that had since been fixed and merged. Before the account team went into a client call, somebody had to confirm the fixes actually worked, in each customer's own workspace and not in a clean test account.
- What I did
- Worked through every item on both lists on staging, in each customer's workspace, through the real screens. Where the right kind of record did not exist I created test data rather than touching the customer's own records. Where a fix was merged but had not reached the staging server, I cherry-picked it onto the staging branch and deployed so it could actually be verified.
- Outcome
- A per-item report in plain English: what was reported, the cause, what changed, the exact steps I followed, and the result. Items that could not be exercised were recorded as such, each with the specific reason, instead of being quietly marked passed. The report went into the client call as it was.
- Extras found
- Two new defects surfaced while verifying the old ones: a required field that claimed it would fill itself and did not, and a template download that failed on its own default setting because the screen and the server disagreed on one value's name.
- Found
- A field the product collected from the user and then never displayed anywhere. Not broken in any way a test would catch: the save succeeded, no error appeared, the data was simply never surfaced again.
- What I did
- Rather than file it and move on, I treated the shape of the defect as a pattern and swept the product for the same shape elsewhere. Captured-but-never-shown gaps do not throw errors, so they survive for months.
- Outcome
- A set of related gaps found in one pass instead of one at a time over a year, and a habit I now apply after every fix: having fixed one instance, look for the same shape elsewhere.
- Problem
- The purchase-order area was large, and customers were finding gaps in it before we were.
- What I did
- Instead of writing test cases from the UI, I started from the code and built a map of every surface the module exposes: every screen, field, status transition and rollup. Then I used the map to find the places with no coverage at all. Testing came second, aimed at the gaps the map exposed.
- Outcome
- A written surface map with the uncovered areas listed and ranked, used to drive verification in priority order rather than testing whatever was in front of me.
- Scope
- A granular role-permission feature spanning every module: each role crossed against each action.
- What I did
- Worked the matrix systematically rather than spot-checking, recording each cell as passed, failed, or blocked, and keeping blocked honestly separate from failed, with the reason attached.
- Outcome
- Roughly a quarter of the matrix came back failing, with per-cell evidence. That kind of result only emerges from working a matrix exhaustively rather than sampling it.
The automation suite
An end-to-end UI suite in Python and Playwright, built with AI assistance. It covers the critical money flows: creating and approving a bill, paying it by each payment method, raising a purchase order from a bid, converting a requisition, recording a deposit. The suite is private; these are the design decisions behind it.
Real clicks only
No script-dispatched clicks and no API calls to set up or shortcut a flow. In this product a scripted click can report success without the handler ever running, so a test that passes that way is lying. Every assertion is against what the screen actually did.
A findings registry
Known defects are recorded in one place and referenced by the tests that hit them, so a known failure stays visible as a known failure instead of either failing the run or quietly masking a new, different break in the same flow.
Written for a staged-rendering app
The app renders in stages and never reaches network idle, so waits are tied to the specific thing being waited for, not to a generic idle signal. Several screens have more than one implementation of the same control, so locators match any of the variants.
Environment-aware
Flows run against the environment the data lives in, with the workspace, supplier and account fixtures held in config rather than hard-coded in tests.
A public version you can watch run
The work suite is private, so I rebuilt the same way of working on public demo sites. These are its 18 tests: the left side lists them, the right side shows what each one does. Type a command or tap a test.
- Run API tests8 tests, no browser
- Run UI tests10 tests in Chromium
- Run Postman collectionwith Newman
- Upload reportsscreenshots and HTML
Your cart
Checkout: your information
The left panel lists my real tests. The right panel is a drawn replay of what each one does, not a live run. See the real runs on GitHub Actions.
What is inside the public repo
Test plan, test cases, automation, defect report and root cause analysis, built with an AI-assisted workflow that is documented in the repo. Pick a folder.
The paperwork, as it sits in the repo
Select a page to read it.
Results
The 15 backend pull requests are the ones that matter most to where I am heading: Kotlin service logic, state machines, and uniqueness and invariant rules, each one written off a defect I had already traced to its cause.
Changes I have shipped
Each of these started from a defect I traced. They were written with Claude Code and reviewed by a developer before merge. The common thread: fix the cause at every place it reaches, not just the symptom that got reported.
Scoping a uniqueness rule correctly
Invoice numbers were unique across the whole organisation when they should only be unique per supplier, so two different suppliers using the same numbering scheme collided.
Adding a missing case to a state machine
The mixed purchase-order completion rule: an order with both materials and services could never complete, because each rollup only recognised its own kind of line.
Enforcing an invariant at every entry point
A material line needs either a part number or a description to be renderable, so the check went in at all four places such a line can be created or edited, rather than just the one the bug was reported against.
Fixing a field mapping between front end and API
A conversion flow read a field name the API never sends, so data silently did not carry across. Nothing errored; the value just was not there.
Making a search actually search a field
A payment's bank confirmation number was stored but neither search path looked at it, so searching by it returned nothing. Both paths plus the search index were updated together so they stay in step.
Surfacing loading and error states
A data fetch discarded its errors, so a failed fetch was indistinguishable from an empty result and a whole form field vanished with no explanation. Now it shows a loading state, and an error with a retry, rather than disappearing.
Refusing an unsafe state transition
Cancelling an approval could leave a bill in a state its payments contradicted, so the reversal now refuses when there is settled money it cannot explain, while still allowing the legitimate case.
Writing honest error messages
Actions that failed silently now say what failed, and a confirmation dialog that said delete for an action that actually revokes an invitation now says what it really does.
How I work
Rules I arrived at from being wrong and correcting it.
Get the source data before judging the output
A blank field or a dash is only a defect if the underlying record actually holds a value. I check the source first. It has saved me from raising defects that were not, and once it reversed a verdict I had already given.
No shortcuts to a green test
I do not call the API to set up or complete a flow an automated test is meant to be exercising, and I do not use scripted clicks that can succeed without the application responding.
Blocked is not failed
If I could not exercise something, I write that, with the specific reason. Inflating a pass rate costs more later than an honest gap does now.
State the blast radius
Every fix says what else the change reaches, so reviewers assess the actual risk rather than the diff size.
Check the thing is really live
A fix can be merged, deployed, and still reach nobody: behind a flag that is off, or on a branch the environment does not run. I confirm the code is actually running where I am testing before I report a result.
After fixing one instance, look for the same shape
Defects that do not throw errors hide for months. Once I know the shape of one, I sweep for others.
Write for the person who has to act
Reports go out in plain English with the steps I followed, so an account manager or an engineer can use them without translation.
Break this cart
This small cart has 3 planted bugs. Use it the way a tester would. When you hit one, the bug report writes itself from the steps you took, in the format I use.
- ID
- Title
- Severity
- Steps
- Expected
- Actual
- Layer
Skills, stated honestly
Split by what I do on my own and what I do with support, so nothing here needs defending later.
Testing
CoreFunctional and exploratory testing, end-to-end flows, regression, matrix and permissions testing, test data design, defect reporting with root cause.
Domain
DeepAccounts payable and receivable, payment methods and batch payments, purchase orders and procurement, general ledger and journal imports, QuickBooks sync, role-based access.
Automation
Core, AI-assistedPython, Playwright, pytest, locator strategy for dynamic UIs, fixtures and config-driven environments. Built with AI assistance, verified by me.
Tooling
CoreGit and GitHub, pull requests and code review, CI runs, branch and environment management, staging deploys, Postman, MongoDB queries, Jira-style tracking, Slack-based delivery.
Tracing code
With AI assistanceKotlin and Spring services, MongoDB documents and queries, React and Next.js components and data fetching: following a symptom to its cause across both layers, with Claude Code.
Shipping fixes
AI-assisted, reviewedBackend service logic, front-end field mapping and error states, unit tests alongside the change. Small and well-scoped, written with Claude Code, reviewed by a developer.
Learning now
In progressWriting Python without AI help, API design, FastAPI and Postgres, and writing backend changes unaided rather than from a traced cause.
Projects
Everything here is public. Open any of them.
playwright-pytest-framework
End-to-end test framework: Playwright UI tests, an API layer with JSON schema checks, a Postman collection run by Newman, and CI on every push. Built with an AI-assisted workflow documented in the repo.
qa-interview-prep
My QA interview study notes, organised by topic so I can revise one area at a time.
python-practice
In progressDaily Python exercises, written from memory and committed one day at a time.
Job application tracker
PlannedA small API to track job applications, with its own test suite. This is my first backend project, starting November 2026.
Hiring for a QA or SDET role?
I am open to QA automation and SDET roles, and to backend-leaning QA work, in Bengaluru or remote. Email is the fastest way to reach me.
ritikchaturvedi.tech@gmail.com