Cosmin Pop
Don’t photocopy your manual tests into Playwright: port by combining, not translating
Aug 7, 2026A manual test case is a unit of manual labor, not of coverage, and automation inverts every economic assumption that shaped it. Porting several hundred manual cases 1:1 into Playwright buries the real coverage under hundreds of tiny specs that each re-pay full login and navigation. The rule I enforced above all: never default…
Strangling two legacy test suites into one repo, without a big-bang rewrite
Aug 6, 2026Two aging Playwright suites for the same platform (a browser E2E suite and a separate API-only suite) had diverged into two repos with their own auth and data hacks, more expensive to touch every quarter. Not a big-bang rewrite and not an in-place refactor: a strangler-fig migration into one repo where both tiers…
Finding the knee: a ramp-to-knee load probe inside your Playwright project
Aug 5, 2026Bumping the worker count made the suite's discovery phase fan a few concurrent GET /orders calls and the shared dev environment sprayed 502s, under modest load. Nobody could answer with a number: at what concurrency does it break? I answered it with ~500 lines of TypeScript inside the Playwright repo I already had…
Lint rules regex can’t write: enforcing test conventions with AST guards
Aug 4, 2026A handful of test conventions lived in a standards doc enforced by code review, so they decayed. Grep-based pre-commit checks failed too: template-literal titles, multi-line calls, and JSDoc prose force regex to choose between missing violations and crying wolf, and either kills trust in the gate. What worked: four small scripts that parse every…
Every test knows its ticket: lightweight traceability annotations for Playwright
Aug 3, 2026Which requirement does this test cover, and which tests cover PROJ-7251? On a regulated-adjacent suite, 'let me check the spreadsheet' isn't an answer. I encode traceability as typed imperative calls inside each test body (trackingRef/testType/severity), mirror them as title tags for grep, report them automatically via an auto-fixture, generate a ticket-to-tests index, and enforce the whole convention with an AST guard in the merge gate. About four lines per test, no test-management plugin.
For a Playwright suite running 24/7 against a shared Cognito+SSO environment, auth, not the product, was the top cause of red runs: tokens expiring mid-run into waves of opaque 401s, or two processes refreshing at once and leaving everyone with a dead token. The fix is a three-layer architecture: validate, refresh, recover…
Net-zero test data: a written cleanup policy for shared test environments
Jul 30, 2026The shared dev order list opened to three pages of garbage: one leaked entity per write-path test per run, piling up since the suite first learned to POST, and tripping the discovery code that finds real entities. The fix wasn't a cleanup sprint but a one-page written policy: net-zero by default with a verified…
Seed data that heals itself: idempotent find-or-create pools for a shared test environment
Jul 30, 2026On a shared dev environment where orders can't be deleted, test data drifts under other teams' deploys and curious humans. Creating fresh entities floods the box; reusing one by name breaks the moment another spec edits it. The pattern that held: find-or-create seed pools keyed on a deterministic title, resolving each slot into one of four outcomes (REUSED, REPAIRED, DEGRADED, CREATED) and repairing drift with targeted idempotent writes. Repair beats recreate.
Page objects that scale in Playwright: a base class, modules, and one promotion rule
Jul 30, 2026Fifty page objects across five domains is exactly the size where the layer usually rots: every entity form a 400-line fork, a utils/ folder nobody owns, specs reaching two layers down. Three structural decisions kept mine from rotting: one abstract base class owning entity-form mechanics so a new form costs ~40 lines not 400,…
Angular Material abstractions make a Playwright suite go green while the app quietly does the wrong thing. The root cause every time is that the visible DOM is not the state the form will submit. fill() leaves a ReactiveForms control pristine so the PATCH drops the field and the backend still returns 200. A…