A production-grade SDET automation framework built with a pure JavaScript stack to automate real-world e-commerce scenarios on Flipkart.com
- ๐ Case Study โ Deep-dive: problem analysis, challenges, solutions & metrics
- Challenge Overview
- Tech Stack
- Architecture
- Project Structure
- Quick Start
- How It Works
- Page Object Model Design
- BDD Scenario
- Data Extraction Strategy
- Environment Configuration
- Test Lifecycle
- Sample Output
- NPM Scripts
- Design Decisions
This framework was built as a 3-hour SDET interview challenge โ ported from the original Java/Selenium stack to a modern JavaScript ecosystem:
| Requirement | Status |
|---|---|
| Build BDD / POM automation framework from scratch | โ |
| Navigate to Flipkart homepage | โ |
| Handle unexpected promotional pop-ups | โ |
| Search for "Shoes for Men" | โ |
| Implement pagination (Page 1 & 2) | โ |
| Extract MRP & Discount Percentage | โ |
| Ensure proper browser teardown | โ |
The original challenge specified Java/Selenium โ this framework reimplements it using a modern JS stack:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Original Stack โ JS Stack โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Java โ Node.js โ
โ Selenium โ Playwright โ
โ TestNG โ Cucumber.js (BDD) โ
โ Maven โ npm โ
โ Eclipse โ VS Code โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
| Technology | Role | Why This Choice |
|---|---|---|
| Node.js | Runtime | Fast, async-native, huge ecosystem |
| Playwright | Browser Automation | Auto-wait, superior popup handling, built-in assertions |
| Cucumber.js | BDD Framework | Gherkin syntax, readable by non-technical stakeholders |
| dotenv | Configuration | Environment-based config (headed/headless, timeouts) |
| Page Object Model | Design Pattern | Maintainable, reusable, scalable test architecture |
graph TD
A["๐ฅ Feature File<br/><i>Gherkin BDD Scenario</i>"] --> B["๐ Step Definitions<br/><i>Maps steps to actions</i>"]
B --> C["๐ Page Objects<br/><i>POM Layer</i>"]
C --> D["๐ญ Playwright<br/><i>Browser Engine</i>"]
D --> E["๐ Flipkart.com<br/><i>Target Application</i>"]
F["โ๏ธ cucumber.js<br/><i>Runner Config</i>"] -.-> B
G["๐ hooks.js<br/><i>Lifecycle Hooks</i>"] -.-> D
H["๐ .env<br/><i>Environment Config</i>"] -.-> G
I["๐ logger.js<br/><i>Output Formatter</i>"] -.-> B
graph LR
subgraph "BDD Layer"
A[Feature Files]
B[Step Definitions]
end
subgraph "POM Layer"
C[BasePage]
D[HomePage]
E[SearchResultsPage]
end
subgraph "Infrastructure"
F[World Context]
G[Hooks]
H[Logger]
end
subgraph "External"
I[Playwright]
J[Flipkart.com]
end
A --> B
B --> D
B --> E
D --> C
E --> C
C --> I
I --> J
F --> D
F --> E
G --> I
B --> H
testkart/
โ
โโโ ๐ package.json # Dependencies & npm scripts
โโโ ๐ cucumber.js # Cucumber runner configuration
โโโ ๐ .env # Environment variables (URL, timeouts, browser)
โโโ ๐ .gitignore # Git exclusions
โโโ ๐ README.md # This file
โ
โโโ ๐ฅ features/ # โโ BDD LAYER โโ
โ โโโ flipkart-shoes.feature # Gherkin scenario (human-readable)
โ
โโโ ๐ step-definitions/ # โโ GLUE CODE โโ
โ โโโ flipkart-shoes.steps.js # Maps Gherkin โ Page Object calls
โ
โโโ ๐ pages/ # โโ PAGE OBJECT MODEL โโ
โ โโโ BasePage.js # Abstract base (navigate, wait, click, screenshot)
โ โโโ HomePage.js # Flipkart home (popup handling, search)
โ โโโ SearchResultsPage.js # Search results (pagination, data extraction)
โ
โโโ โ๏ธ support/ # โโ FRAMEWORK INFRASTRUCTURE โโ
โ โโโ world.js # Custom Cucumber World (browser + data store)
โ โโโ hooks.js # Before/After hooks (launch, teardown, screenshots)
โ
โโโ ๐ utils/ # โโ UTILITIES โโ
โ โโโ logger.js # Formatted console tables & statistics
โ
โโโ ๐ reports/ # Generated HTML/JSON test reports
โโโ ๐ธ screenshots/ # Failure screenshots (auto-captured)
graph TD
hooks["hooks.js"] --> world["world.js"]
hooks --> dotenv[".env"]
world --> HomePage["HomePage.js"]
world --> SearchResultsPage["SearchResultsPage.js"]
HomePage --> BasePage["BasePage.js"]
SearchResultsPage --> BasePage
steps["flipkart-shoes.steps.js"] --> world
steps --> logger["logger.js"]
feature["flipkart-shoes.feature"] --> steps
cucumber["cucumber.js"] --> feature
cucumber --> steps
cucumber --> hooks
style feature fill:#23D96C,color:#fff
style BasePage fill:#2563EB,color:#fff
style HomePage fill:#3B82F6,color:#fff
style SearchResultsPage fill:#3B82F6,color:#fff
style hooks fill:#F59E0B,color:#fff
style world fill:#F59E0B,color:#fff
- Node.js v18+ installed
- npm v9+ installed
# 1๏ธโฃ Clone and enter the project
cd testkart
# 2๏ธโฃ Install dependencies
npm install
# 3๏ธโฃ Install Playwright's Chromium browser
npm run install:browsers
# 4๏ธโฃ Run the test suite (headed mode โ watch the browser)
npm test
# 5๏ธโฃ Or run in headless mode (for CI/CD)
npm run test:headlesssequenceDiagram
participant C as ๐ฅ Cucumber Runner
participant H as ๐ Hooks
participant S as ๐ Step Definitions
participant P as ๐ Page Objects
participant B as ๐ญ Playwright Browser
participant F as ๐ Flipkart.com
C->>H: Before Hook
H->>B: Launch Chromium
B-->>H: Browser Ready
C->>S: Given: Navigate to homepage
S->>P: HomePage.open()
P->>B: page.goto(flipkart.com)
B->>F: HTTP Request
F-->>B: HTML Response
B-->>P: Page Loaded
C->>S: And: Handle pop-ups
S->>P: HomePage.dismissPopup()
P->>B: Escape / Close Button
B-->>P: Popup Dismissed
C->>S: When: Search "Shoes for Men"
S->>P: HomePage.searchProduct()
P->>B: Fill & Submit
B->>F: Search Request
F-->>B: Results Page
C->>S: Then: Extract data (Page 1)
S->>P: SearchResultsPage.extractProductData(1)
P->>B: page.evaluate() โ DOM scan
B-->>P: Product Array [40 items]
C->>S: When: Navigate to Page 2
S->>P: SearchResultsPage.goToPage(2)
P->>B: Click pagination / URL param
B->>F: Page 2 Request
F-->>B: Page 2 Results
C->>S: Then: Extract data (Page 2)
S->>P: SearchResultsPage.extractProductData(2)
P->>B: page.evaluate() โ DOM scan
B-->>P: Product Array [40 items]
C->>S: Then: Display consolidated results
S->>S: Logger.displayConsolidatedResults()
Note over S: ๐ 80+ products displayed
C->>H: After Hook
H->>B: page.close() โ context.close() โ browser.close()
B-->>H: โ
Teardown Complete
The POM pattern encapsulates all page interactions into reusable, maintainable classes:
classDiagram
class BasePage {
+Page page
+int defaultTimeout
+navigate(url)
+getTitle()
+getCurrentUrl()
+waitForPageLoad()
+waitForElement(selector)
+isElementVisible(selector)
+click(selector)
+type(selector, text)
+getText(selector)
+takeScreenshot(name)
}
class HomePage {
+String searchInput
+String popupCloseButton
+open()
+dismissPopup()
+searchProduct(term)
}
class SearchResultsPage {
+String productCard
+String sellingPrice
+String originalMRP
+hasSearchResults()
+extractProductData(pageNum)
+goToPage(targetPage)
+getCurrentPageNumber()
+scrollToLoadAllProducts()
}
BasePage <|-- HomePage : extends
BasePage <|-- SearchResultsPage : extends
| Page Object | Responsibilities | Key Methods |
|---|---|---|
| BasePage | Navigation, waits, clicks, screenshots | navigate(), waitForPageLoad(), isElementVisible() |
| HomePage | Flipkart homepage interactions | open(), dismissPopup(), searchProduct() |
| SearchResultsPage | Product data & pagination | extractProductData(), goToPage(), scrollToLoadAllProducts() |
The test scenario is written in Gherkin โ a human-readable format that serves as both documentation and executable specification:
@flipkart @shoes @e2e
Feature: Flipkart Men's Shoes โ Search & Data Extraction
Scenario: Extract MRP and Discount Percentage from Page 1 and Page 2
Given I navigate to the Flipkart homepage
And I handle any promotional pop-ups
When I search for "Shoes for Men"
Then I should see search results for shoes
When I scroll to load all products on the current page
Then I extract MRP and Discount Percentage from page 1
And I display the results for page 1
When I navigate to page 2
Then I should be on page 2
When I scroll to load all products on the current page
Then I extract MRP and Discount Percentage from page 2
And I display the results for page 2
Then I display the consolidated results from all pagesFeature File (WHAT) โ Step Definitions (HOW) โ Page Objects (WHERE)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
"I navigate to homepage" โ Given step โ HomePage.open()
"I handle pop-ups" โ Given step โ HomePage.dismissPopup()
"I search for X" โ When step โ HomePage.searchProduct(X)
"I extract data from N" โ Then step โ SearchResultsPage.extractProductData(N)
"I navigate to page N" โ When step โ SearchResultsPage.goToPage(N)
"display consolidated" โ Then step โ Logger.displayConsolidatedResults()
Flipkart uses obfuscated CSS class names (e.g., _2WkVRV, Nx9bqj) that change frequently with every deployment. Traditional selector-based scraping breaks constantly.
Instead of relying on class names, our extraction uses content patterns and CSS computed styles:
flowchart TD
A["๐ Scan ALL div/span elements"] --> B{"Contains<br/>'XX% off'?"}
B -->|Yes| C["๐ Found Discount Element"]
B -->|No| D["Skip"]
C --> E["โฌ๏ธ Walk UP DOM tree<br/>to find product card container"]
E --> F{"Has image +<br/>โน price +<br/>height 100-800px?"}
F -->|Yes| G["โ
Found Product Card"]
F -->|No| H["โฌ๏ธ Go up one more level"]
H --> F
G --> I["Extract from card:"]
I --> J["๐ฐ Selling Price<br/><i>โนXXX without strikethrough</i>"]
I --> K["๐ท๏ธ MRP<br/><i>โนXXX with line-through CSS</i>"]
I --> L["๐ Discount<br/><i>XX% off text</i>"]
I --> M["๐ Title<br/><i>Link text > 10 chars</i>"]
style C fill:#23D96C,color:#fff
style G fill:#2563EB,color:#fff
style J fill:#F59E0B,color:#000
style K fill:#EF4444,color:#fff
style L fill:#8B5CF6,color:#fff
| Data Point | Detection Method |
|---|---|
| Discount | Regex: /^\d+%\s*off$/i on leaf elements |
| Selling Price | Regex: /^โน[\d,]+$/ + NO text-decoration: line-through |
| MRP | Regex: /^โน[\d,]+$/ + HAS text-decoration: line-through |
| Product Title | <a> tag text > 10 chars, not starting with โน or % |
| Product Card | Ancestor with <img> + price elements + height 100-800px |
All runtime settings are controlled via the .env file:
# Flipkart Base URL
BASE_URL=https://www.flipkart.com/
# Search Configuration
SEARCH_TERM=Shoes for Men
# Browser Configuration
HEADLESS=false # true = CI mode, false = watch the browser
SLOW_MO=100 # Delay between actions (ms) for visibility
VIEWPORT_WIDTH=1440 # Browser window width
VIEWPORT_HEIGHT=900 # Browser window height
# Pagination
MAX_PAGES=2 # Number of pages to scrape
# Timeouts (ms)
DEFAULT_TIMEOUT=30000 # Wait timeout for elements
NAVIGATION_TIMEOUT=45000 # Page navigation timeoutgraph LR
ENV[".env File"] --> hooks["hooks.js<br/><i>dotenv.config()</i>"]
hooks --> world["world.js"]
hooks --> base["BasePage.js"]
hooks --> home["HomePage.js"]
world --> |HEADLESS| B1["Browser Mode"]
world --> |SLOW_MO| B2["Action Speed"]
world --> |VIEWPORT_*| B3["Window Size"]
world --> |*_TIMEOUT| B4["Page Timeouts"]
base --> |DEFAULT_TIMEOUT| B5["Element Waits"]
base --> |NAVIGATION_TIMEOUT| B6["goto() Timeout"]
home --> |BASE_URL| B7["Flipkart URL"]
style ENV fill:#F59E0B,color:#000
style B1 fill:#3B82F6,color:#fff
style B2 fill:#3B82F6,color:#fff
style B3 fill:#3B82F6,color:#fff
stateDiagram-v2
[*] --> BeforeHook: Scenario Start
BeforeHook --> BrowserLaunch: launchBrowser()
BrowserLaunch --> ContextCreate: newContext(viewport, userAgent)
ContextCreate --> PageCreate: newPage()
PageCreate --> TestExecution: Page Objects Ready
TestExecution --> ScenarioPass: All Steps โ
TestExecution --> ScenarioFail: Step Failure โ
ScenarioFail --> ScreenshotCapture: page.screenshot()
ScreenshotCapture --> AfterHook
ScenarioPass --> AfterHook
AfterHook --> PageClose: page.close()
PageClose --> ContextClose: context.close()
ContextClose --> BrowserClose: browser.close()
BrowserClose --> [*]: Teardown Complete โ
The framework uses a multi-strategy fallback approach for popup dismissal:
Strategy 1: CSS class-based close button โ Try first
โ (failed)
Strategy 2: XPath-based close button โ Fallback
โ (failed)
Strategy 3: Generic text-based close button โ Broader match
โ (failed)
Strategy 4: Escape key press โ Universal fallback
โ (failed)
Result: Log "No popup detected" and continue โ Graceful handling
Strategy 1: Click page number link directly โ Most precise
โ (failed)
Strategy 2: Click "Next" button โ Common pattern
โ (failed)
Strategy 3: URL parameter manipulation โ Most reliable fallback
(?page=2 appended to URL)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ TestKart โ Launching Browser
โโ Mode: Headed
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Navigating to: https://www.flipkart.com/
โ
Page loaded: Online Shopping Site for Mobiles...
๐ Checking for promotional pop-ups...
โ
Pop-up dismissed using: Escape key
๐ Searching for: "Shoes for Men"
โ
Search results loaded for: "Shoes for Men"
โ
Search results are displayed
๐ฆ Extracting product data from Page 1...
โ
Extracted 45 products from Page 1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ PAGE 1 RESULTS โ 45 Products Found โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโฌโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโโโโ
โ โ # โ Product โ Price โ MRP โ Discount โ
โโโโโผโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโค
โ 0 โ 1 โ Exclusive Trendy Sports... โ โน256 โ โน1,299 โ 80% off โ
โ 1 โ 2 โ Trendy & Stylish Runnin... โ โน240 โ โน999 โ 75% off โ
โ 2 โ 3 โ ES-21 Hockey Walking/... โ โน452 โ โน1,999 โ 77% off โ
โ...โ... โ ... โ ... โ ... โ ... โ
โโโโโดโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโโโ
๐ Navigating to Page 2...
โ
Navigated to Page 2
๐ฆ Extracting product data from Page 2...
โ
Extracted 45 products from Page 2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ CONSOLIDATED RESULTS โ ALL PAGES โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ ๐ Page 1: 45 products โ
โ ๐ Page 2: 45 products โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ ๐ฆ Total Products Extracted: 90 โ
โ ๐ฐ Products with MRP: 90 โ
โ ๐ท๏ธ Products with Discount: 80 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ฏ Total products extracted: 90
โ
Data extraction complete!
๐งน Tearing down browser...
โ
Browser teardown complete
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ TestKart โ Test Execution Complete
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Reports: reports/cucumber-report.html
๐ธ Screenshots: screenshots/
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
1 scenario (1 passed)
13 steps (13 passed)
0m37.400s
| Script | Command | Description |
|---|---|---|
npm test |
npx cucumber-js |
Run all BDD scenarios (headed) |
npm run test:headed |
HEADLESS=false npx cucumber-js |
Explicitly headed mode |
npm run test:headless |
HEADLESS=true npx cucumber-js |
Headless mode (CI/CD) |
npm run install:browsers |
npx playwright install chromium |
Download Chromium |
npm run pretest |
mkdir -p reports screenshots |
Create output directories |
| Feature | Selenium | Playwright |
|---|---|---|
| Auto-wait for elements | โ Manual waits | โ Built-in |
| Popup handling | โ Complex | โ Native support |
page.evaluate() for DOM |
โ Limited | โ Full browser context |
| Network interception | โ Requires proxy | โ Built-in |
| Speed | ๐ข Slower | ๐ Faster |
| Multi-browser support | โ Yes | โ Yes |
Flipkart uses obfuscated, randomly-generated CSS class names that change with every deployment (e.g., _2WkVRV, Nx9bqj, slAVV4). Our approach:
- โ
Class-based selectorsโ Break with every Flipkart deploy - โ Content patterns (โน symbol, % off, strikethrough CSS) โ Resilient to UI changes
The World object serves as a shared context across all step definitions:
World = {
browser, // Playwright Browser instance
context, // Browser Context (viewport, cookies)
page, // Active Page
homePage, // HomePage POM instance
searchResultsPage, // SearchResultsPage POM instance
productsData: [] // Accumulated product data across pages
}
// pages/ProductDetailPage.js
const BasePage = require('./BasePage');
class ProductDetailPage extends BasePage {
constructor(page) {
super(page);
// Define locators
}
// Define methods
}
module.exports = ProductDetailPage;# features/flipkart-electronics.feature
@electronics
Scenario: Search and extract laptop prices
Given I navigate to the Flipkart homepage
When I search for "Laptops"
Then I extract pricing data from page 1Built with โค๏ธ for the SDET Community
TestKart โ Because real SDET interviews test what you can build, not what you can memorize.