Wednesday, August 19, 2026

14 Painful Playwright Mistakes to Avoid


Someone on r/Playwright asked a good question: what painful mistakes should you avoid? Forty-plus engineers answered — people running Playwright against real production, not demos. I read the whole thread and pulled out the mistakes that came up again and again.

None of this is theory. It's scar tissue. Most of these are the difference between a suite that works on your laptop and one that survives CI, real traffic, and a login wall. Here they are, each with the fix the thread landed on.

01 Hard-coded waits everywhere

The most-cited mistake by a mile: waitForTimeout(3000) sprinkled through the suite to "make it stable." It passes locally because your machine is fast, then breaks in CI because the container is slower. You've traded a real fix for a timer that's wrong on both ends — too slow when things are fine, too fast when they aren't.

FIX → Trust Playwright's auto-waiting. Use web-first assertions like expect(locator).toBeVisible() and let the framework wait for actionability.

02 Brittle selectors

Targeting something like div.css-1a2b3c > span:nth-child(3) is asking for pain. That selector encodes your DOM's exact shape and a hashed class name — both change the moment a developer touches the component. Your test breaks, and it isn't even a real bug.

FIX → Use getByRole, getByText, and getByTestId. They describe intent, not structure, so they survive UI refactors.

03 Over-abstracting the Locator API

This one drew a crowd. Playwright's Locator API is already rich, but people wrap it in layers of helpers anyway — defining a locator, then an accessor method, then a method that calls the accessor. One commenter described building a beautiful page-object hierarchy in year one and spending year two deleting it. The worst offenders come from Selenium and bring fifteen years of habits to a framework that already does the work.

FIX → Default to Playwright's built-ins. Delete a wrapper before you add one — earn every layer of abstraction.

04 Trusting storageState naively

A sharp production story: relying on storageState while the app rotates tokens or triggers step-up auth mid-session. Tests pass in staging and fail live, because the real auth flow only kicks in against production. Anything behind a real login is exposed to this.

FIX → Keep saved auth ephemeral, not permanent. Add a cheap validity check — hit an API endpoint and expect a 200 — before the suite leans on it.
05

05 Running the full suite serially in CI

Someone ran their suite serially for nine months to "save resources." The result: 90-minute pipelines, and a 1-in-50 flake dragging average merge time to roughly 2.5 hours. Moving to parallel workers with proper isolation cut it to twelve minutes — and made flakes diagnosable on their own instead of as a cascade.

FIX → Run parallel workers with real test isolation from day one. Serial-to-save-money almost always costs more.

06 Building test data through the UI

Clicking through signup to create a user for every test is slow, flaky, and couples your setup to the very UI you're testing. When it breaks, the whole run cascades and you can't tell setup failures from real ones.

FIX → Pre-create users and state via API, not the UI. Every test gets its own clean data, and failures stay isolated.

07 Screenshot diffs full of noise

A first visual baseline that picked up a relative timestamp ("2 hours ago") and a chart that animated over 800ms. Every diff came back 100% noise, and the team learned to ignore visual failures — which defeats the point of having them.

FIX → Make the page deterministic first. Freeze the clock in beforeEach, set animation-duration: 0, and mask anything backed by a third-party widget.

08 Treating test code as second-class

The most underrated point in the thread: suites die from neglect more often than from any selector strategy. Tests that never get reviewed or refactored rot fast. A test goes red on a Friday, someone adds test.skip() with a comment nobody revisits, and after enough of those the suite is a vibes-only signal.

FIX → Review, own, and prune test code like production code. Discipline beats framework choice every time.

09 Shipping codegen output as-is

Record-and-playback is genuinely useful, but the generated code is a starting point, not a suite. Ship it raw and you inherit brittle selectors and hard waits by default — the exact things at the top of this list.

FIX → Treat codegen as a scaffold. Rewrite the selectors and structure before you commit it.

10 Ignoring production realities

Real traffic brings slower API responses, rate limits, and captcha or MFA challenges that never existed in lower environments. A suite tuned only against a quiet staging box falls over the first time it meets production conditions.

FIX → Simulate production-like load before release, monitor slow endpoints, and plan for the auth challenges you'll actually hit.

11 Wasting type safety in TypeScript

If you're on TypeScript but writing it like untyped JavaScript — any everywhere, no typed page objects, no typed fixtures — you've kept the compile step and thrown away the reason for it.

FIX → Type your fixtures, helpers, and data. Let the compiler catch the mistakes before the test run does.

12 Over-testing

Chasing a coverage number with low-value tests that don't map to real user flows. Each one is a maintenance liability forever, and together they slow the suite and bury the failures that matter.

FIX → Cover the flows that matter and skip the vanity ones. Fewer, meaningful tests beat a wall of green noise.
13

Blindly relying on AI-generated code

AI writes Playwright fast, and that's exactly the trap. The output looks plausible, so it lands without a second read — and quietly ships the brittle selectors and hard waits from the top of this list. When an agent generates your suite and nobody reviews it, you don't have tests, you have a pile of guesses that happened to pass once. The thread's take was blunt: the discipline that keeps production code clean matters more than whatever tool wrote it.

FIX → Treat AI output like a junior dev's pull request. Read every line, prune it, and make sure you understand it before it lands.
14

Not learning Playwright properly in the first place

You can absolutely piece this together from Reddit threads, docs, and trial-and-error in CI. It works — slowly, and usually after you've made most of the thirteen mistakes above yourself. The faster path is a structured one that covers the whole picture: end-to-end, API testing, and the AI and MCP side of modern automation, in the order that actually builds on itself.

FIX → Learn it in one structured path — my Playwright TypeScript Automation Testing: E2E, API, AI, MCP course.

The one that split the room

Page objects: discipline or dead weight?

The thread agreed on almost everything above. On one topic it went to war: the Page Object Model. Half the room called skipping POM the cardinal sin. The other half argued that fixating on OOP layers rots suites just as fast — factory functions are simpler, and heavy class hierarchies fight you when the app changes constantly.

The most interesting version of the argument tied it to AI: when an agent is generating your test code and your pages change weekly, deep abstraction becomes a liability, not an asset. There's no settled answer here — which is exactly why it's worth thinking through for your own suite instead of copying a template. Where do you land?

If there's a thread running through all twelve, it's this: the framework isn't your problem. Determinism, isolation, and treating tests like real code are. Get those right and most of this list never happens to you.

I go deep on the right way to do this — auto-waiting, resilient locators, API-driven setup, and CI — in my Playwright with TypeScript course, including the AI and MCP side of modern test automation. If this list was useful, the course is the structured version of it. Check it out here.

Friday, August 14, 2026

How to Install Claude Code (CLI + VS Code Extension) for Playwright MCP

Before You Start: Installing Claude Code

The lectures in this section use Claude Code to drive the Playwright MCP server. Before the next video, take five minutes to install it and sign in. Everything else in this section builds on these steps.

Read this first

Course URL playwright.testautomationtv.com

Claude Code requires a paid Anthropic account — a Pro, Max, Team, or Enterprise Claude subscription, or a Claude Console (API) account. The free Claude.ai plan does not include Claude Code access.

I am telling you this up front so it is not a surprise halfway through an install. If you would rather not subscribe, read the section "If you are using a different AI tool" at the bottom of this article — the Playwright MCP is an open protocol and it is not tied to Claude.

You already installed Node.js in Section 1. If you skipped that lecture, install the Node.js LTS version now — the npm install option below needs Node.js 22 or later.

Step 1 — Install the Claude Code CLI

Open a terminal and run the command for your operating system.

macOS, Linux, or WSL:

curl -fsSL https://claude.ai/install.sh | bash

Windows PowerShell:

irm https://claude.ai/install.ps1 | iex

If you are in PowerShell, your prompt starts with PS C:\. If it starts with C:\ without the PS, you are in the older Command Prompt — open PowerShell instead and run the command above.

If you prefer npm (any operating system, Node.js 22 or later):

npm install -g @anthropic-ai/claude-code

Do not put sudo in front of the npm command. It causes permission problems later.

Step 2 — Confirm the install worked

claude --version

You should see a version number printed back, something like 2.1.211 (Claude Code). If you see command not found, close the terminal, open a new one, and try again — the installer adds Claude Code to your PATH, and an already-open terminal will not have picked that up.

If it still fails, run this command, which prints a diagnostic report:

claude doctor

Step 3 — Sign in

From your project folder, start Claude Code:

claude

The first run opens your browser and asks you to authorise the account. Complete that, return to the terminal, and you are logged in. You only do this once per machine.

Step 4 — Install the VS Code extension

Claude Code VS Code Extension


You need VS Code 1.94.0 or higher.

  1. In Visual Studio Code, press Ctrl+Shift+X on Windows and Linux, or Cmd+Shift+X on Mac, to open the Extensions view.

  2. Search for Claude Code.

  3. Click Install on the extension published by Anthropic.

  4. If the extension does not appear afterwards, restart VS Code, or open the Command Palette and run Developer: Reload Window.

Open the panel by clicking the Claude Code icon in the Activity Bar on the left, or by clicking Claude Code in the status bar at the bottom right. Sign in when prompted — same account as Step 3.

An important point about the extension

The VS Code extension and the command-line tool are two separate things. The extension gives you the chat panel inside the editor. It does not give you the claude command in your terminal.

In the next lecture we register the Playwright MCP server by running a terminal command that starts with claude. That command comes from the CLI you installed in Step 1, so please do not skip Step 1 and install only the extension.

Your checklist before the next lecture

  • claude --version prints a version number

  • Running claude in a terminal opens an interactive session

  • The Claude Code panel opens inside VS Code and shows you as signed in

If all three are true, you are ready.

One thing that will save you time later

When you register an MCP server, an already-open Claude session will not always see it. If claude mcp list shows the server as connected but your assistant cannot reach it, close the session and start a new one. This comes up again later in the section, and it is almost always the explanation.

If you are using a different AI tool

The Model Context Protocol is an open standard, and Playwright's MCP server is not tied to Claude. Other agentic coding tools support MCP as well — some register a server with a single command like the one you will see in the next lecture, and others expect you to edit a configuration file directly.

The concepts in this section — what the MCP server does, how it reads the accessibility tree, how to turn an exploration into a real test file — apply the same way regardless of which assistant you use. The commands on screen are the Claude Code versions, so check your own tool's documentation for its equivalent registration step.

Official documentation

Installation steps change from time to time. If a command in this article does not work, the current instructions are always here:

See you in the next lecture.

Naeem Malik — Course URL playwright.testautomationtv.com

Saturday, August 8, 2026

Playwright + TypeScript, from Zero to AI-Powered E2E Testing

Playwright with Typescript Udemy Course Overview


I just launched a new Udemy course, "Playwright with TypeScript – Automation Testing, E2E, API & AI/MCP," and I wanted to write a bit about why I built it, what's inside, and where you can grab the code that goes along with every lecture.

🚀 Enroll in the course on Udemy  |  💻 Get the companion code on GitHub

Why Playwright, and why now?

Playwright has quietly become one of the most reliable tools for end-to-end testing — fast, resilient to flakiness, and genuinely pleasant to write tests in once you get past the first few locators. But most tutorials stop at "click a button, assert some text." Real test suites need to survive real applications: dialogs, iframes, drag-and-drop, shopping carts, custom fixtures, and CI pipelines that don't fall over every time the UI shifts a pixel.

This course is built around that gap. It starts from the fundamentals and works all the way up to using AI — specifically the Playwright MCP server and Claude Code — to generate and even self-heal failing tests inside GitHub Actions.

What you'll learn

  • Writing reliable E2E tests with Playwright + TypeScript, from locators to assertions
  • Handling real-world UI patterns: checkboxes, radios, drag-and-drop, dialogs, iframes
  • Building maintainable test suites with custom fixtures and the Page Object pattern
  • Testing a full shopping cart flow end-to-end
  • Debugging with traces, the HTML reporter, and soft assertions
  • Generating and healing tests with AI, using the Playwright MCP server and Claude Code
  • Wiring up a self-healing CI workflow so failing tests fix themselves via GitHub Actions

The companion repo

Every lecture has real, working code behind it, all in one place:

github.com/naeemakram/playwright-typescript-e2e-ai

tests/
  cart/            # Full shopping cart E2E test suite
  *.spec.ts        # Section-by-section lecture exercises
specs/             # AI-generated test plans (used with the Playwright MCP planner)
.github/workflows/ # CI, including an AI-powered self-healing test workflow
playwright.config.ts  # Base URL, projects, tracing, and reporter setup
.mcp.json          # Playwright MCP server config used for AI test generation

Commits roughly track the course section-by-section, so you can check out the history and watch each feature get built incrementally rather than just staring at a finished pile of code. Clone it, follow along, and — most importantly — break things and fix them. That's still the fastest way to actually learn a testing tool.

Getting started with the code

npm install
npx playwright install
npx playwright test

And once a run finishes, open the HTML report to see exactly what happened:

npx playwright show-report

Who this is for

If you've never written an automated test before, you'll be fine — the course starts at the very beginning. If you already know Playwright but haven't touched fixtures, Page Objects, or CI, there's a lot here for you too. And if you're curious how AI tooling like MCP and Claude Code fits into a real test workflow rather than just being a novelty, the later sections are built specifically for that.

Let's go

Enroll in the course on Udemy and pair it with the GitHub repo as you go. If you find it useful, a rating or review genuinely helps other learners find it — thank you in advance! ⭐⭐⭐⭐⭐

Friday, August 7, 2026

AI Model Collapse

Machine Learning & AI

Understanding AI Model Collapse: What Happens When AI Feeds on AI?

What happens when synthetic data floods the internet, and future models train primarily on AI-generated content? Welcome to the challenge of Model Collapse.

Udemy Course: Playwright TypeScript Automation Testing


AI Model Collapse


Think of model collapse like making a photocopy of a photocopy. Each successive generation introduces minor errors, hallucinations, and bias amplifications that compound over time—until the system completely loses touch with reality.

1. The Mechanism: How Collapse Occurs

Researchers studying recursive training loops have identified two distinct stages of degradation when AI systems are trained on outputs from older AI models:

Stage 1: Early Collapse (Tail Loss)

The model begins losing its grasp on rare facts, edge cases, and nuanced details—the tails of the statistical distribution curve. While general fluency remains intact, niche expertise vanishes.

Stage 2: Late Collapse (Systemic Breakdown)

The model entirely disconnects from real-world data structures. Outputs become hyper-generic, highly repetitive, and fundamentally ungrounded from factual reality.

2. The Core Risks

Allowing model collapse to go unchecked poses significant challenges for digital ecosystems and enterprise deployments:

  • Knowledge Collapse: The AI maintains fluent, grammatically flawless syntax while becoming completely unreliable regarding facts underneath.
  • Homogenization & Loss of Diversity: Outputs lose stylistic variance and unique cultural perspectives, flattening into predictable, generic prose.
  • Bias Amplification: Systemic biases present in initial training datasets are magnified exponentially as models repeatedly digest their own assumptions.

3. Prevention & Architectural Fixes

While commercial frontier models are not in full collapse today, preventing this "hall of mirrors" outcome requires proactive system architecture:

Strategy Mechanism & Purpose
Human-in-the-Loop (HITL) Injecting high-quality, verified human-generated data to periodically recalibrate model weights and maintain baseline reality.
Retrieval-Augmented Generation (RAG) Anchoring LLM outputs against external, authoritative databases rather than relying purely on fixed internal parameter storage.
Data Provenance & Verification Implementing cryptographic tracking for original web data and deploying multi-agent verification pipelines to cross-check factual consistency.

The Long-Term Engineering Reality

Preventing model collapse isn't just an abstract data science concern—it is a fundamental software quality and data governance challenge. Keeping artificial intelligence grounded requires rigorous data pipeline management and continuous validation.

How is your engineering team auditing and verifying training data or live inputs in your AI workflows?

Saturday, August 1, 2026

The Death of Script Maintenance: Why AI-Era Engineers Write "Toilet Paper Code"

The Rise of Toilet Pager Code in AI Era

 

Udemy Course: Playwright TypeScript Automation Testing

Executive Summary: For decades, software engineering operated under a fundamental rule: Every line of code is an asset that must be maintained, architected, and checked into source control. Today, generative AI has crushed the marginal cost of code creation to zero. As a result, code is transforming from a durable asset into a single-use utility—what I call "Toilet Paper Code." Here is why this shift is happening, how to leverage it safely, and where the boundary lies between disposable utility and toxic tech debt.

The Old Paradigm: Code as a Durable Asset

Historically, writing code carried high cognitive and temporal overhead.

Even for trivial tasks—formatting a messy 50,000-row CSV file, parsing legacy test execution logs, or transforming a complex JSON payload—an engineer had to make a calculated decision: Is it worth spending 45 minutes writing and debugging a Python script to save 20 minutes of manual labor?

More often than not, the math didn't add up. When we did write those scripts, we treated them like durable assets. We created dedicated git repositories, set up virtual environments, added argparse options, handled every theoretical edge case, and wrapped them in documentation.

We were conditioned to believe that all code must be built to last.

[Traditional Mindset]
Intent ──> High Overhead ──> Architect ──> Test ──> Commit to Git ──> Long-term Maintenance

The AI Shift: The Zero-Cost Marginal Line

Generative AI inverts this dynamic completely.

When an LLM can craft a context-aware 30-line Python script or Bash pipeline in under 3 seconds, the friction between intent and execution disappears.

[AI-Era Mindset]
Intent ──> Prompt LLM ──> Execute Code in REPL/CLI ──> Consume Result ──> Discard

When code creation cost approaches zero, code itself ceases to be the product or the deliverable. It becomes a transient runtime query language—an ephemeral bridge to get from raw input to desired output.

This gives rise to Toilet Paper Code(term coined by Naeem Malik in 2026): code that is context-sensitive, hyper-specific, run once (or a few times), and immediately thrown away.

The 4 Golden Rules of Toilet Paper Code

Not all disposable code is created equal. To practice this pattern effectively without creating chaos, top-tier engineers stick to four core principles:

1. Hyper-Contextual

Toilet Paper Code solves only the specific data, schema, or system state in front of you at this exact moment. It doesn't care about backward compatibility, modularity, or parameterization.

2. Ephemeral Execution

It lives in temporary buffers—an interactive Python REPL, a throwaway terminal tab, or a scratchpad directory. You run it, grab the output, and close the session.

3. Zero Maintenance Overhead

Forget PEP 8 perfection, custom exception classes, or 100% test coverage. The sole metric of success is simple: Did it execute once without throwing an unhandled exception?

4. Outcome-Focused

The code isn't the artifact. The scrubbed dataset, the parsed report, or the bulk-renamed file structure is the artifact. Once the outcome is achieved, keeping the code around yields negative value.

Real-World Use Cases in Quality Engineering & Automation

As Quality Engineers and Automation Leads, we deal with immense operational noise. Here is how "Toilet Paper Code" accelerates day-to-day work:

Example: Ad-Hoc Data Scrubbing for Automation Suites

Imagine you need to seed your staging database with 1,000 user profiles extracted from a legacy production dump, but the JSON format is nested incorrectly for your target API.

In the past, you'd spend an hour configuring an ETL script or custom parser. Today, you dump a sample payload into an AI prompt and execute a disposable script:

Python
# Disposable Script: Clean Legacy JSON for Staging Seed
# Lifetime: 45 seconds. Status: Discard after execution.

import json

with open("raw_dump.json", "r") as f:
    raw = json.load(f)

cleaned = [
    {
        "user_id": item["meta"]["id"],
        "email": item["contact"]["email_primary"],
        "active_flag": True if item["status"] == "A" else False
    }
    for item in raw["data"] if "contact" in item
]

with open("cleaned_seed.json", "w") as f:
    json.dump(cleaned, f, indent=2)

print(f"Successfully processed {len(cleaned)} records.")

You run python script.py, get cleaned_seed.json, verify the output, and delete script.py. Total elapsed time: 90 seconds.

The Danger Zone: When Toilet Paper Code Kills Production

The single biggest risk of disposable code is context leakage.

When a throwaway script performs a data migration or log calculation exceptionally well, engineers are often tempted to say: "Hey, that worked great! Let's wrap a FastAPI endpoint around it, push it to main, and call it a microservice."

This is how technical debt explodes overnight. Disposable code lacks:

  • Boundary validation and schema enforcement.

  • Rate limiting and memory leak guards.

  • Robust error logging and telemetry.

  • Security sanitization against injection attacks.

The Golden Rule of Engineering Maturity:

Use Toilet Paper Code freely in isolated sandboxes (CLIs, scratchpads, local automation tasks). But the moment code moves into a shared codebase or production pipeline, it must be rewritten from scratch with durable engineering standards.

Conclusion: The Modern Engineer's Superpower

The shift toward disposable code isn't about promoting sloppy development—it's about frictionless velocity.

Knowing when to build a decade-long durable framework versus when to write a 30-second throwaway script is the hallmark of a senior engineer in the AI era. Stop over-engineering your throwaway tasks, embrace disposable utilities, and reserve your deep architectural energy for the systems that actually matter.

About the Author

Naeem Malik

Senior Quality Engineer & AI Systems Specialist

I specialize in test automation architecture, quality engineering leadership, and building pragmatic AI-driven developer workflows.

  • 🌐 Website / Portfolio: demo.testautomationtv.com

  • 💼 Open to Work: Available for Remote QE & AI Engineering roles globally (via Deel).

  • 📬 Connect: Reach out on LinkedIn to discuss software testing, AI automation, or open engineering roles.

  • The term toilet paper code was coined by Naeem Malik, an automation testing specialist AI builder.