MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

When Confidence Meets Production: Lessons from a Missing Slash

2025-11-12 17:19:32

Confidence Coverage: When 94% Tests Still Break Production

It was one of those Friday nights that make you feel invincible.
Every test passed. Every coverage metric gleamed above 90%.
The CI pipeline was all green — the kind of green that makes you think, “Yeah, we nailed it.”

As a senior engineer working in Trade Finance, where a single API call can move millions, that confidence felt well earned.

I had done my homework:

  • Unit tests ✅
  • Integration tests ✅
  • Regression suite ✅
  • Manual sanity ✅

I pushed to production, shut my laptop, and packed for a weekend trek up Agasthyakoodam.
Confidence? Sky-high. The kind that only comes from seeing everything green.

The Ping That Shattered the Peace

Saturday, 8:07 AM.
Fresh mountain air. Backpack on. First step up the trail.

📱 Buzz.

“Production is down. URGENT.”

I froze.
No signal. No backup. Just me, a laptop, and a mountain.

I climbed faster — not for the view, but for network bars.
By the time I reached 6,000 feet, I had a faint 4G signal.

Sitting on a rock, I opened my laptop to find the logs that would ruin my morning.

The Culprit: A Slash Too Few

The logs told a simple, painful story.
An API call failed — triggering a domino of retries, timeouts, and broken workflows.
The cause? A missing trailing slash in an environment variable.

Here’s what went wrong:

  • Staging: Host URL ended with a slash (https://api.example.com/), endpoint started clean (status). ✅
  • Production: Host had no trailing slash (https://api.example.com), endpoint began without one (status). ❌

When the two joined, the resulting URL was malformed.
The urljoin() call, which had behaved perfectly in staging, broke in production.

A single missing slash — and a multi-million-dollar workflow came to a halt.

My 94% test coverage didn’t see it coming.

The Illusion of Confidence

That was the day I learned:
Test coverage ≠ confidence coverage.

Unit tests make you feel safe.
Integration tests make you feel thorough.
But production? It humbles you.

Because:

  • Mocks don’t drift. Real environments do.
  • Test data behaves. Real data doesn’t.
  • CI is clean. Configuration is chaos.

The irony? The tests weren’t wrong — they did their job.
They just didn’t prepare me for reality.

Why It Hurt More in Trade Finance

In Trade Finance, every integration layer is mission-critical.
Dozens of systems — banks, partners, repositories, regulators — all stitched together through APIs and assumptions.

A single malformed URL isn’t just a 500 error.
It’s a delay in document exchange.
A payment that doesn’t clear.
A customer commitment that slips.

When one link fails, the entire chain rattles.
And your “all green” test suite? It won’t catch it.

Confidence Coverage > Code Coverage

That morning on the mountain changed my mental model.
Forget chasing 100% code coverage — start measuring confidence coverage.

Confidence coverage is the degree of assurance that your system will behave correctly under real conditions, not just ideal ones.

It’s built through:

  • Broader integration and end-to-end testing
  • Environment consistency checks
  • Automated configuration validation
  • Continuous monitoring and alerting
  • Load and chaos testing in staging

When you think in terms of confidence coverage, the question shifts from:

“How many lines are tested?”
to
“How sure am I that this won’t blow up in production?”

What I’d Tell My Younger Self

  1. Testing isn’t about perfection — it’s about preparation.
    Your tests won’t catch every failure, but they’ll teach you how to handle them gracefully.

  2. Environment parity matters more than coverage percentages.
    A missing slash, a timeout, a wrong env var — these break real systems.

  3. Integrate early. Integrate often.
    The earlier you touch real systems, the fewer weekend calls you’ll get.

  4. Monitor everything.
    Tests prevent some fires. Monitoring tells you when one’s already burning.

  5. Confidence is good. Humility is better.
    Every “impossible” bug reminds you: software at scale isn’t deterministic — it’s probabilistic with flair.

The View from the Peak

I fixed the issue right there on the mountain — half a battery, one patch, and a heavy dose of humility later.
The system came back up.
The trek continued.
The view at the top? Worth every step.

But something changed in how I saw engineering.

  • Unit tests are your seatbelt.
  • Integration tests are your guardrails.
  • Monitoring is your lookout tower.
  • And confidence? It’s the illusion you earn — until production reminds you who’s really in charge.

Information is not knowledge And Knowledge Is Not Wisdom

2025-11-12 17:18:17

We live in the so-called “Information “Age”—when nearly every fact, figure, or opinion is just a click away. Search engines answer questions in seconds, and AI tools can summarize textbooks in minutes. Yet, despite this digital abundance, the difference between information, knowledge, and wisdom has never been more crucial—or more misunderstood.

The Click Illusion

“Knowing information doesn’t mean acquiring knowledge. And knowledge without wisdom is like having tools without knowing how or when to use them.”

In today’s hyper-connected world, many confuse access to information with understanding. A quick Google search or an AI-generated answer might feel like knowledge, but it’s often shallow, decontextualized, and detached from lived experience.

Why Wisdom Can’t Be Googled

True wisdom is earned. It emerges slowly, often painfully, through lived experiences, failures, and reflection. It involves context, nuance, and the ability to make sound judgments—things that no amount of raw data or AI summaries can replace.

Information is data—disconnected facts.
Knowledge is understanding—when those facts are connected and made sense of.
Wisdom is application—when that knowledge is used with judgment, timing, and empathy.
Immanuel Kant explained the process of acquiring real knowledge with precision:

“Percepts without concepts are blind; concepts without percepts are empty.”

In simple terms, experience without understanding leads nowhere, and understanding without experience is hollow.

The Wisdom of Not Knowing

Lao Tzu, the ancient Chinese philosopher, captured the essence of true learning:

“Not knowing is true knowledge. Presuming to know is a disease.” — Tao Te Ching

In other words, the moment we assume we know, we block the path to deeper learning. Accepting “not knowing” is the key to real growth. It opens the mind to curiosity, humility, and lifelong learning. In contrast, the overuse of AI and the internet for instant answers can reinforce superficial understanding and sometimes even arrogance.

The Dangers of Superficial Knowing

The rampant use of AI and the internet for “quick answers” has led to:

Loss of depth in learning—memorization without true understanding
Overconfidence in half-truths—a false sense of expertise
Dependency on automation—weakening critical thinking and original thought
Neglect of experience—forgetting that some things can only be learned by doing

Conceptual Understanding is Key

Whether you’re a student, a doctor, a writer, or a decision-maker, real understanding means going beyond the surface. It means asking “why,” grappling with uncertainty, challenging assumptions, and being okay with not knowing.

How to Move from Information to Wisdom

  1. Slow down the learning process. Don’t just consume—reflect.

  2. Seek experiences, not just answers. Learn by doing, failing, and questioning.

  3. Use AI and internet tools as companions, not crutches.

  4. Respect the unknown. Humility is the foundation of wisdom.

  5. Learn from the wise. Elders, mentors, and hands-on practitioners carry insights no algorithm can replicate.

Conclusion

In an age of instant answers, true learners are those who embrace the long road—the one paved with curiosity, experience, and reflection. Let’s not mistake speed for depth or data for truth. Because ultimately, wisdom is not downloaded—it is lived.

As Lao Tzu reminds us, accepting not knowing is not a weakness—it is the beginning of true knowledge.

Build an AI Summarizer Agent in TypeScript Using Mastra (Part 2): Connect to Frontend

2025-11-12 17:13:54

Series Navigation: Part 1: Building the Agent | Part 2: Connect to Frontend (You are here)

In Part 1, we built a fully functional AI summarizer agent that can fetch content from URLs and generate intelligent summaries.
In this follow-up, you’ll learn how to connect that agent to a Next.js frontend using the Mastra Client, bringing your summarizer to life with a simple web interface.
Here’s what the final result looks like Ai-summarizer-agent.com:

AI Summarizer

Prerequisites

  • Completed Part 1 of this series
  • Basic knowledge of React and Next.js
  • The AI Summarizer Agent from Part 1 (code available on GitHub)

Setting Up Next.js with Mastra

Let's start by setting up a Next.js project that integrates with our Mastra agent.

Step 1: Create a Next.js Project

If you're starting fresh, create a new Next.js app:

npx create-next-app@latest ai-summarizer-app
cd ai-summarizer-app

When prompted, select:

  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: Yes
  • src/ directory: Yes
  • App Router: Yes
  • Import alias: No (or default)

Step 2: Install Mastra Client

The Mastra Client allows your frontend to communicate with your agents:

npm install @mastra/core

Step 3: Copy Your Agent Code

Copy your agent code from Part 1:

  1. Copy the entire src/mastra folder from Part 1
  2. Place it in your new Next.js project at src/mastra
  3. Copy your .env file with the Gemini API key

Your project structure should now look like:

ai-summarizer-app/
├── src/
│   ├── app/
│   │   ├── api/
│   │   │   └── summarize/
│   │   │       └── route.ts
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── components/
│   │   └── SummarizerUI.tsx
│   ├── mastra/
│   │   ├── agents/
│   │   │   └── summarizer.ts
│   │   ├── tools/
│   │   │   └── web-scraper.ts
│   │   └── index.ts
├── .env
├── package.json
└── tsconfig.json

Creating the API Route

Next.js API routes will act as the bridge between your frontend and the Mastra agent.

Step 1: Create the Summarize API Route

Create a new file at src/app/api/summarize/route.ts:

import { summarizerAgent } from '@/src/mastra/agents/summarizer';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  try {
    const { input } = await request.json();

    if (!input || typeof input !== 'string') {
      return NextResponse.json(
        { error: 'Input is required', success: false },
        { status: 400 }
      );
    }

    const result = await summarizerAgent.generate(input, {
      maxSteps: 5,
    });

    return NextResponse.json({
      text: result.text,
      success: true,
    });
  } catch (error) {
    return NextResponse.json(
      {
        error: error instanceof Error ? error.message : 'Failed',
        success: false,
      },
      { status: 500 }
    );
  }
}

What's happening here:

  • We accept a POST request with an input field
  • We validate that input exists and is a string.
  • We call the summarizerAgent with the input and an optional parameter called maxSteps (maxSteps defines how many “reasoning steps” the agent can take while generating the summary)
  • We return the summarized text as a JSON response for real-time updates.
  • We handle errors gracefully

Part 3: Building the Frontend UI

Now let's create an interactive UI for our summarizer.

Step 1: Create the Summarizer Component

Create a new file at src/components/SummarizerUI.tsx:

'use client';

import { useState } from 'react';

type InputMode = 'url' | 'text';

export default function SummarizerUI() {
  const [input, setInput] = useState('');
  const [summary, setSummary] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [mode, setMode] = useState<InputMode>('url');

  const handleSummarize = async () => {
    if (!input.trim()) {
      setError('Please enter a URL or text to summarize');
      return;
    }

    setLoading(true);
    setError('');
    setSummary('');

    try {
      const response = await fetch('/api/summarize', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ input }),
      });

      if (!response.ok) {
        throw new Error('Failed to generate summary');
      }

      const data = await response.json();

      if (data.success) {
        setSummary(data.text);
      } else {
        setError(data.error || 'Failed to generate summary');
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Something went wrong');
    } finally {
      setLoading(false);
    }
  };

  const handleKeyPress = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && !e.shiftKey && mode === 'url') {
      e.preventDefault();
      handleSummarize();
    }
  };

  return (
    <div className="max-w-4xl mx-auto p-6 space-y-6">
      {/* Header */}
      <div className="text-center space-y-2">
        <h1 className="text-4xl font-bold text-gray-900">AI Summarizer Agent</h1>
      </div>

      {/* Mode Selector */}
      <div className="flex gap-2 justify-center">
        <button
          onClick={() => setMode('url')}
          className={`px-4 py-2 rounded-lg font-medium transition-colors ${
            mode === 'url'
              ? 'bg-blue-600 text-white'
              : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
          }`}
        >
          URL
        </button>
        <button
          onClick={() => setMode('text')}
          className={`px-4 py-2 rounded-lg font-medium transition-colors ${
            mode === 'text'
              ? 'bg-blue-600 text-white'
              : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
          }`}
        >
          Text
        </button>
      </div>

      {/* Input Area */}
      <div className="space-y-2">
        <label className="block text-sm font-medium text-gray-700">
          {mode === 'url' ? 'Enter URL' : 'Enter Text'}
        </label>
        {mode === 'url' ? (
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyPress={handleKeyPress}
            placeholder="https://example.com/article"
            className="w-full px-4 py-3 border text-black border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
            disabled={loading}
          />
        ) : (
          <textarea
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="Paste your text here..."
            rows={6}
            className="w-full px-4 py-3 border text-black border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
            disabled={loading}
          />
        )}
      </div>

      {/* Submit Button */}
      <button
        onClick={handleSummarize}
        disabled={loading || !input.trim()}
        className="w-full bg-blue-600 text-white py-3 px-6 rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
      >
        {loading ? (
          <span className="flex items-center justify-center gap-2">
            <svg className="animate-spin h-5 w-5" viewBox="0 0 24 24">
              <circle
                className="opacity-25"
                cx="12"
                cy="12"
                r="10"
                stroke="currentColor"
                strokeWidth="4"
                fill="none"
              />
              <path
                className="opacity-75"
                fill="currentColor"
                d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
              />
            </svg>
            Generating Summary...
          </span>
        ) : (
          'Summarize'
        )}
      </button>

      {/* Error Message */}
      {error && (
        <div className="p-4 bg-red-50 border border-red-200 rounded-lg">
          <p className="text-red-800 text-sm">
            <span className="font-medium">Error:</span> {error}
          </p>
        </div>
      )}

      {/* Summary Output */}
      {summary && (
        <div className="space-y-3">
          <h2 className="text-xl font-semibold text-gray-900">Summary</h2>
          <div className="p-6 bg-gradient-to-br from-blue-50 to-indigo-50 border border-blue-200 rounded-lg">
            <div
              className="prose prose-sm max-w-none text-gray-800"
              dangerouslySetInnerHTML={{
                __html: summary.replace(
                  /\*\*(.*?)\*\*/g,
                  '<strong>$1</strong>'
                ),
              }}
            />
          </div>
        </div>
      )}

      {summary && (
        <button
          onClick={() => {
            setSummary('');
            setInput('');
            setError('');
          }}
          className="text-sm text-gray-600 hover:text-gray-900 underline"
        >
          Clear and start over
        </button>
      )}

    </div>
  );
}

What's happening here:

  • We create a React component with state management for input, summary, loading, error, and mode (which toggles between 'url' and 'text' input types)
  • We build a handleSummarize function that sends a POST request to /api/summarize with the user's input and updates the UI based on the response
  • We render a clean interface with mode selector buttons, dynamic input fields (text input for URLs, textarea for text), and a submit button with loading states
  • We display the generated summary in a styled container with basic Markdown formatting (converting bold syntax to HTML)
  • We handle errors gracefully by showing error messages in a red alert box and include a "Clear and start over" button to reset the interface

AI Summarizer agent

Step 2: Update the Home Page

Update src/app/page.tsx to use our new component:

import SummarizerUI from '@/components/SummarizerUI';

export default function Home() {
  return (
    <main className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 py-12">
      <SummarizerUI />
    </main>
  );
}

Step 3: Update the Layout (Optional)

Update src/app/layout.tsx to add metadata:

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: 'AI Summarizer | Powered by Mastra',
  description: 'Intelligent content summarization using AI agents',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

Testing Your Application

Let's test everything works correctly.

Run the Development Server

npm run dev

Open http://localhost:3000 in your browser.

Test URL Summarization

  1. Click the "URL" button
  2. Enter: https://blog.google/technology/ai/google-gemini-ai/
  3. Click "Summarize"
  4. Watch the agent fetch and summarize the content

URL Summarization

Test Text Summarization

  1. Click the " Text" button
  2. Paste a long article or text
  3. Click " Summarize"
  4. See the immediate summary generation

Text Summarization

Test Error Handling

  1. Try an invalid URL
  2. Try empty input
  3. Verify error messages display correctly

Error Handling

Conclusion

You've successfully connected your Mastra AI agent to a beautiful Next.js frontend. You now have a production-ready application that:

What You've Learned

  • Mastra Client Integration: How to connect frontend to an agents
  • Next.js API Routes: Building backend endpoints for AI agents
  • Modern UI/UX: Creating intuitive interfaces for AI applications

Complete Code Repository

The full working code for both Part 1 and Part 2 is available on GitHub:

Repository: github.com/yourusername/ai-summarizer-mastra

Additional Resources

Documentation

Happy building!

If you found this tutorial helpful, consider sharing it with other developers learning AI agent development!

When You Should “Wet” Your Code: Why Blindly Following DRY Can Hurt Your Project

2025-11-12 17:06:15

The DRY principle (Don’t Repeat Yourself) is probably one of the first rules we learn as developers. Its idea seems flawless: don’t repeat code, don’t duplicate knowledge, and move everything that repeats into a single place.

In practice, this means that if a piece of logic appears in multiple places, it should be centralized - as a function, class, module, or component.

This makes the project cleaner, easier to maintain, and safer to modify. Fix a bug in one place - and it disappears everywhere.

In business logic, especially on the backend, DRY works beautifully. Data validation, discount calculations, and authorization rules are perfect examples of knowledge that must be centralized. When such logic is scattered across different services, inconsistencies and bugs are inevitable. In these cases, DRY can literally save months of work, make the system predictable, and simplify refactoring.

But my personal experience - and I mostly work with startups and fast-changing frontend projects — has shown that this principle, when applied dogmatically, can do real damage. When requirements change every week and design lives its own life, strict DRY often becomes a development bottleneck rather than a path to clean code. The more “mature” a project becomes, the “drier” it can be. But in the early stages, too much dryness can be fatal.

DRY in Theory: The Original Idea and Its Strengths

DRY wasn’t created as a fight against repeating code just for aesthetics - it was a fight against repeating knowledge. The authors of the principle, Andrew Hunt and David Thomas, explained in The Pragmatic Programmer that if the same business rule appears in multiple places, it’s a sign of an architectural flaw.

The core idea is simple:

  • Every piece of knowledge should exist in one and only one place.
  • Changing that knowledge should require changing only one fragment of code.

This ensures consistency, reduces the risk of bugs, and simplifies testing. DRY makes code stable and transparent when it comes to recurring data structures, formulas, business rules, calculations, or constants.

Where DRY Really Works

Everything related to backend logic is where DRY shines. For example:

  • shared database access functions
  • centralized error handling
  • unified pricing and discount calculation rules
  • recurring data models and validation

In these areas, duplicating logic leads to divergence and unpredictable behavior. Here, DRY doesn’t just save lines of code — it preserves the integrity of the system’s meaning.

When DRY Turns Into a Trap

The Risk of Over-Abstraction and Poor Readability

The biggest mistake is applying DRY automatically without context.

The desire to “combine everything” often creates monstrous abstractions.

Developers merge similar chunks of code and end up with a giant function full of conditions, flags, and optional parameters that tries to do everything — but no one remembers exactly what.

Such “universal” modules become black boxes: hard to modify, painful to debug, and confusing to newcomers.

Once, I worked on a project where I just needed to change the color of a button on one page. But the button component was so “dried up” — with factories, dozens of props, and global styles — that it took me four hours to make a tiny change. Ironically, there were only about ten buttons in the entire interface. That was a textbook example of how blind DRY can turn simplicity into chaos.

Tight Coupling and Fragile Design

When everything depends on one shared module, every small change becomes a minefield. You fix something for one page — and something breaks elsewhere. This creates tight coupling, where independent parts of the system become hostages of shared logic.

Ironically, the famous DRY argument “fix it once, fix it everywhere” works in reverse: introduce a bug once — and it spreads everywhere. The result is a fragile, rigid system where touching one line can cause a chain reaction.

Premature Abstraction – the Enemy of Flexibility

DRY was never meant to eliminate every visual repetition — only repetition of knowledge. But developers often merge pieces that are only visually similar, not conceptually identical. At first, it feels smart: “Why write this twice?”. Then you realize these pieces evolve differently, and the abstraction becomes a burden.

In startups, I see this all the time. Requirements change weekly, designs daily. Premature abstraction slows everything down — you keep adding exceptions, flags, and branching logic just to preserve a “shared” structure.

It’s often far easier to duplicate a small piece of code, let it evolve independently, and only later refactor it once the functionality stabilizes. This follows the “Rule of Three”: if you write the same thing three times, that’s the moment to abstract.

Architectural Traps and the “Distributed Monolith”

In microservice architectures, DRY can be downright dangerous.

Developers create shared libraries “to avoid duplication,” but in reality, they end up with a distributed monolith — a system where all services depend on one shared core.

Now you can’t deploy anything independently; every minor update risks breaking multiple components. Over time, those shared “commons” libraries turn into dumping grounds for everything no one dares to delete. Instead of elegance, DRY breeds technical debt.

Frontend teams are especially vulnerable: shared “UI utility” libraries quickly become bloated, outdated, and terrifying to touch. Everything looks dry, but nobody understands how it works anymore.

When DRY Conflicts With Common Sense

In the real world, systems are rarely symmetrical or stable.

In frontend projects especially, “universal components” often become nightmares: a single mega-component for buttons, taking ten props and supporting countless variants — until someone clones it anyway to make “a rounded one with a gradient hover.”

When the designer suddenly wants three shapes of buttons, forty color shades, and unique hover states, the DRY dream collapses. Sometimes it’s far better to have three separate, simple components than one monstrous one.

In young, evolving projects, universal abstractions don’t speed things up — they create inertia. When the priority is speed, iteration, and experimentation, duplication is often cheaper and safer.

When It’s Better to “Wet” Your Code

Sometimes, duplication is an intentional, pragmatic decision.

If similar pieces of code serve different purposes, evolve at different paces, or belong to different contexts — keep them separate. This mindset even has a name: WET — Write Everything Twice.

It’s not an argument for chaos — it’s an acknowledgment that duplication is often cheaper than the wrong abstraction. This is especially true in frontend development and young products, where requirements change faster than architecture can stabilize.

In business logic and backend development, DRY still shines — for calculations, validation, and data access layers. But in user interfaces and early-stage products, pragmatism must win over dogma.

Conclusion

DRY is an excellent principle — when used consciously. It keeps systems consistent and maintainable. But when treated like a religion, it becomes a source of rigidity and pain.

In frontend development and fast-moving projects, dogmatic DRY causes more harm than good. The faster your product evolves, the more important flexibility, independence, and clarity become.

Let the code repeat a little — as long as it stays readable, local, and easy to change. After all, programming isn’t about eliminating repetition — it’s about balancing clarity, speed, and common sense.

Manual to Automation — the transformation every company needs!

2025-11-12 17:06:02

We’re excited to share that our CubenSquare experts have recently been onboarded as consultants for a leading tech company to regulate and automate their entire testing process.
Our team is currently implementing a complete shift — from manual testing to automated frameworks using Selenium, Python, and CI/CD pipelines to ensure faster, smarter, and more reliable deployments. 🚀

And here’s the best part —
You can learn directly from these experts who are handling real-time automation consulting projects right now!

Our Software Testing Training Program is designed to take you from scratch to hands-on expertise:
Manual + Automation Testing (Selenium / PyTest / API Testing)
CI/CD Integration with Jenkins & GitHub
Real-time Project Scenarios & Internship
Job Assistance + Resume & Mock Interview Support

Get free Internship ( 1 month) + AI topics combo this October

Random test

2025-11-12 17:05:03

In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.In addition to images for the post's content, you can also drag and drop a cover image.