<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: muhammad Sanaev</title>
    <description>The latest articles on DEV Community by muhammad Sanaev (@muhammadjon_sanaev).</description>
    <link>https://dev.to/muhammadjon_sanaev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3893526%2F360faaf3-3a6c-441f-98ef-70e70b99b2c2.jpg</url>
      <title>DEV Community: muhammad Sanaev</title>
      <link>https://dev.to/muhammadjon_sanaev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhammadjon_sanaev"/>
    <language>en</language>
    <item>
      <title>Why QA Engineers Should Learn Playwright MCP</title>
      <dc:creator>muhammad Sanaev</dc:creator>
      <pubDate>Mon, 25 May 2026 18:21:01 +0000</pubDate>
      <link>https://dev.to/muhammadjon_sanaev/why-qa-engineers-should-learn-playwright-mcp-nbf</link>
      <guid>https://dev.to/muhammadjon_sanaev/why-qa-engineers-should-learn-playwright-mcp-nbf</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;How I used Cursor, Playwright MCP, and Playwright CLI to build real automation for SwiftCart&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AI will not magically turn weak tests into strong automation. But it can help QA engineers move faster when it is used the right way.&lt;/p&gt;

&lt;p&gt;Recently, I built a small automation project for a SwiftCart e-commerce app using Playwright, TypeScript, Cursor, Playwright MCP, Context7 MCP, and GitHub Actions CI.&lt;/p&gt;

&lt;p&gt;The biggest lesson was simple:&lt;/p&gt;

&lt;p&gt;Playwright MCP does not replace Playwright. It helps you inspect the app faster so you can create better Playwright tests.&lt;/p&gt;

&lt;p&gt;That difference matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is Playwright&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Playwright MCP lets Cursor interact with the browser through tools like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;browser_navigate
browser_click
browser_type
browser_snapshot
browser_wait_for
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regular Playwright runs your test files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright MCP helps during development. It lets Cursor open the app, inspect flows, understand locators, and help generate test code.&lt;/p&gt;

&lt;p&gt;Simple way to remember it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Playwright MCP = helps inspect and build tests
Playwright CLI = runs the final tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The website itself is not using MCP. Cursor is using MCP to inspect the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why QA engineers should care&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A big part of automation is not just writing code. It is deciding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What flow should be tested?&lt;/li&gt;
&lt;li&gt;Which locator is stable?&lt;/li&gt;
&lt;li&gt;What should be asserted?&lt;/li&gt;
&lt;li&gt;Is this a real bug or a weak test?&lt;/li&gt;
&lt;li&gt;Can this test run in CI?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP helps with the discovery part. It can inspect the app and help generate a first version faster.&lt;/p&gt;

&lt;p&gt;But the QA engineer still has to review, clean, refactor, and validate the test.&lt;/p&gt;

&lt;p&gt;AI can speed up the work. It should not replace judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What I automated in SwiftCart&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I tested three common e-commerce flows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search flow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;Homepage → Search input → Results page → Product results visible&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add to cart flow
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Products page → Product detail → Add to cart → Cart page → Item visible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Checkout and login validation
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cart → Checkout → Login required → Checkout page → Form validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These are realistic flows that QA teams often automate in e-commerce applications.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;My Workflow&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The workflow was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Use Playwright MCP to inspect the app
2. Discover user flows and locators
3. Generate initial Playwright tests
4. Run tests with Playwright CLI
5. Fix failures
6. Refactor into Page Object Model
7. Add test.step() for better reporting
8. Add GitHub Actions CI
9. Extend into API testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key point: I did not treat AI output as final code. I used it as a starting point, then cleaned it into a maintainable automation framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Api Testing Extension&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After the UI tests, I also created an API testing repo using Playwright request API.&lt;/p&gt;

&lt;p&gt;UI test:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Open browser → click buttons → check page behavior&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
API test:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Send request directly to backend → check status code and JSON response&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
For SwiftCart, I tested endpoints like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /products
GET /products/{id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API tests checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status code is 200&lt;/li&gt;
&lt;li&gt;response is JSON&lt;/li&gt;
&lt;li&gt;products exist&lt;/li&gt;
&lt;li&gt;product fields exist&lt;/li&gt;
&lt;li&gt;invalid product IDs return 404&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;API tests are much faster because they do not open a browser.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>playwright</category>
      <category>automation</category>
      <category>qa</category>
    </item>
    <item>
      <title>Context7 MCP: Real-Time Library Docs for Your AI Coding Assistant (And Why Your QA Workflow Needs It)</title>
      <dc:creator>muhammad Sanaev</dc:creator>
      <pubDate>Sat, 23 May 2026 21:08:46 +0000</pubDate>
      <link>https://dev.to/muhammadjon_sanaev/stop-letting-ai-guess-your-playwright-tests-use-context7-mcp-3k0i</link>
      <guid>https://dev.to/muhammadjon_sanaev/stop-letting-ai-guess-your-playwright-tests-use-context7-mcp-3k0i</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;While working on my Swiftcart QA automation project, I asked my AI agent a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What’s your knowledge cutoff date?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It answered: &lt;strong&gt;April 2024&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That matters because Playwright changes fast. New locator patterns, MCP workflows, test runner updates, and best practices can change after the model’s training cutoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Context7 MCP is
&lt;/h2&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://context7.com/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontext7.com%2Fopengraph-image.png%3F913a90853d344a57" height="838" class="m-0" width="1600"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://context7.com/" rel="noopener noreferrer" class="c-link"&gt;
            Context7 - Up-to-date documentation for LLMs and AI code editors
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Pull up-to-date, version-specific documentation and code examples for any library directly into Cursor, Claude Code, Windsurf, and other AI coding tools.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontext7.com%2Ffavicon.ico" width="74" height="75"&gt;
          context7.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Context7 MCP is a documentation server for AI coding assistants.&lt;/p&gt;

&lt;p&gt;Instead of letting the model answer only from old training data, Context7 allows the agent to fetch current library documentation.&lt;/p&gt;

&lt;p&gt;For my workflow, I used it with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursor&lt;/li&gt;
&lt;li&gt;Playwright&lt;/li&gt;
&lt;li&gt;Context7 MCP&lt;/li&gt;
&lt;li&gt;Playwright MCP&lt;/li&gt;
&lt;li&gt;Swiftcart demo app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters for QA automation
&lt;/h2&gt;

&lt;p&gt;AI can generate tests quickly, but it may also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;suggest outdated syntax&lt;/li&gt;
&lt;li&gt;hallucinate APIs&lt;/li&gt;
&lt;li&gt;create brittle locators&lt;/li&gt;
&lt;li&gt;miss current Playwright best practices&lt;/li&gt;
&lt;li&gt;generate tests that pass but don’t test the right thing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Context7 does not replace knowing Playwright. It gives the AI better context.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I connected Context7 MCP
&lt;/h2&gt;

&lt;p&gt;In my project, I created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.vscode/mcp.json

{
  "servers": {
    "context7": {
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp"]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Confirmation&lt;/strong&gt;&lt;br&gt;
After restarting Cursor, I asked the agent:&lt;/p&gt;

&lt;p&gt;Use Context7 MCP specifically.&lt;br&gt;
Do not use web search.&lt;br&gt;
Resolve the Playwright library ID, then fetch locator docs.&lt;br&gt;
List the exact Context7 tool names used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good sign is seeing tools like:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;context7: resolve-library-id&lt;br&gt;
context7: query-docs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swiftcart app&lt;br&gt;
↓&lt;br&gt;
Playwright MCP inspects the real page&lt;br&gt;
↓&lt;br&gt;
Context7 MCP provides current Playwright docs&lt;br&gt;
↓&lt;br&gt;
Cursor generates the first test draft&lt;br&gt;
↓&lt;br&gt;
I review locators and assertions&lt;br&gt;
↓&lt;br&gt;
Final tests run with Playwright CLI&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>ai</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>The REST Assured Setup Nobody Shows You: Handling Auth Tokens That Expire Mid-Suite</title>
      <dc:creator>muhammad Sanaev</dc:creator>
      <pubDate>Thu, 23 Apr 2026 06:27:07 +0000</pubDate>
      <link>https://dev.to/muhammadjon_sanaev/how-to-set-up-rest-assured-with-testng-and-maven-2026-guide-2o17</link>
      <guid>https://dev.to/muhammadjon_sanaev/how-to-set-up-rest-assured-with-testng-and-maven-2026-guide-2o17</guid>
      <description>&lt;p&gt;Most REST Assured tutorials show you a single given().when().then() against a sample API and call it done. That's fine for learning the syntax, but it doesn't cover what you actually need on a real project  things like config per environment, clean test structure, and handling auth tokens that expire while the suite is running.&lt;/p&gt;

&lt;p&gt;I'm Mukhammadjon Sanaev, a QA Automation Engineer in San Francisco. I've worked across e-commerce, logistics, and sports tech. This post walks through a simple REST Assured + TestNG + Maven setup I'd use on day one of a new API testing project, plus one problem I ran into on a real checkout API that isn't in the tutorials.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A small Java project that:&lt;/p&gt;

&lt;p&gt;Uses REST Assured for API calls&lt;br&gt;
Uses TestNG as the test runner&lt;br&gt;
Runs against dev or staging with a single command&lt;br&gt;
Handles an auth token that expires mid-run&lt;/p&gt;

&lt;p&gt;Examples are based on an e-commerce checkout API — the kind of thing you'd test at a Shopify- or Wayfair-style company. Nothing proprietary, just the shape of a real checkout flow.&lt;/p&gt;

&lt;p&gt;Project Structure&lt;br&gt;
Keep it simple on day one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api-tests/
├── pom.xml
├── testng.xml
└── src/test/
    ├── java/com/example/tests/
    │   ├── BaseTest.java
    │   ├── TokenManager.java
    │   └── CheckoutTests.java
    └── resources/
        └── config.properties

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 1: pom.xml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;xml&lt;span class="nt"&gt;&amp;lt;project&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;modelVersion&amp;gt;&lt;/span&gt;4.0.0&lt;span class="nt"&gt;&amp;lt;/modelVersion&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.example&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;api-tests&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;properties&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;maven.compiler.source&amp;gt;&lt;/span&gt;17&lt;span class="nt"&gt;&amp;lt;/maven.compiler.source&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;maven.compiler.target&amp;gt;&lt;/span&gt;17&lt;span class="nt"&gt;&amp;lt;/maven.compiler.target&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.rest-assured&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;rest-assured&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;5.4.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.testng&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;testng&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;7.10.2&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Config File&lt;br&gt;
Under src/test/resources/config.properties:&lt;br&gt;
properties&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;base.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://api-dev.example.com&lt;/span&gt;
&lt;span class="py"&gt;auth.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://auth-dev.example.com/oauth/token&lt;/span&gt;
&lt;span class="py"&gt;client.id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-client-id&lt;/span&gt;
&lt;span class="py"&gt;client.secret&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-client-secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tip: don't commit real secrets to git. In a real project, read client.secret from an environment variable instead.&lt;/p&gt;

&lt;p&gt;Step 3: BaseTest&lt;br&gt;
Sets the base URL for every test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;javapublic&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@BeforeSuite&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;RestAssured&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseURI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://api-dev.example.com"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4: The Auth Problem&lt;br&gt;
Here's the naive way to handle auth, which most tutorials show:&lt;br&gt;
java// Fetch the token once, reuse forever&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;basic&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"secret"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/auth/token"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonPath&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"access_token"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for 10 tests. It breaks when your suite gets bigger.&lt;br&gt;
The Problem&lt;br&gt;
On one project, our API tokens expired after 15 minutes. Our regression suite took about 22 minutes to run. The first batch of tests passed fine, then around test 140 everything started failing with 401 Unauthorized — not because the code was wrong, but because the token had expired halfway through the run.&lt;br&gt;
The fix isn't to make the suite shorter. The fix is making the framework aware that tokens expire.&lt;br&gt;
The Fix: A Simple TokenManager&lt;/p&gt;

&lt;p&gt;java&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenManager&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;expiresAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isAfter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expiresAt&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;refresh&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grant_type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"client_credentials"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client_id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-client-id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client_secret"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-client-secret"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://auth-dev.example.com/oauth/token"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonPath&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"access_token"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;expiresIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonPath&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expires_in"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Refresh 60 seconds early to avoid edge cases&lt;/span&gt;
        &lt;span class="n"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;plusSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expiresIn&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth noting:&lt;/p&gt;

&lt;p&gt;The 60-second buffer. If you refresh exactly when the token expires, you can still hit a race condition with the server clock. Refreshing a bit early avoids that.&lt;br&gt;
It only refreshes when needed. Most tests just grab the cached token.&lt;/p&gt;

&lt;p&gt;Using It&lt;br&gt;
Every API call pulls a fresh token through TokenManager:&lt;br&gt;
javapublic class CheckoutTests extends BaseTest {&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;getCart_returnsItems&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;TokenManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToken&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pathParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cartId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cart-123"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/cart/{cartId}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"items"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hasSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greaterThan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 5: Running It&lt;br&gt;
testng.xml:&lt;/p&gt;

&lt;p&gt;xml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;suite&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"API Tests"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;test&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Checkout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;classes&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;class&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"com.example.tests.CheckoutTests"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/classes&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/test&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/suite&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
bash&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mvn test&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;That's it a working API test suite that doesn't fall over when tokens expire.&lt;br&gt;
What I'd Add Next&lt;br&gt;
This is a starting point, not the finished framework. Once the basics work, I'd add:&lt;/p&gt;

&lt;p&gt;Separate config files for dev, staging, and prod&lt;br&gt;
JSON schema validation on responses&lt;br&gt;
An HTML report like Allure or Extent Reports&lt;br&gt;
CI/CD integration with Jenkins or GitHub Actions&lt;/p&gt;

&lt;p&gt;The Takeaway&lt;br&gt;
The tricky parts of API automation aren't the tools REST Assured, TestNG, and Maven are straightforward once you've set them up once. The tricky parts are the problems that only show up when a real suite runs against a real API: auth tokens expiring, environment config drift, response schemas changing silently.&lt;br&gt;
If you're setting this up for the first time, start small. Get one test running, handle auth properly, then grow from there.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>java</category>
      <category>qa</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
