Building a Cross-Repository Test Automation Pipeline: From Manual QA Nightmares to Automated Excellence

Kunal Singh

Aug 20, 2025

Building a Cross-Repository Test Automation Pipeline: From Manual QA Nightmares to Automated Excellence

TL;DR

Transformed our SDK release process from manual, error-prone testing to a fully automated cross-repository testing pipeline using GitHub Actions and Playwright. Result: 80% reduction in testing time, zero release-blocking bugs in the last 6 months, and engineers who actually sleep well during release weeks.

The Problem: When SDK Testing Becomes a Bottleneck

Your team maintains a JavaScript SDK used by hundreds of applications. Every release means manually testing core functionality, web popups, inbox features, and API integrations across different versions and environments.

Our pre-automation reality:

  • 2-3 days of manual testing per release
  • Multiple environments to validate (development, staging, various SDK versions)
  • Human error leading to missed edge cases
  • Release anxiety because something always broke in production
  • Delayed releases due to testing bottlenecks

The Solution: A Cross-Repository Automation Architecture

We built a system where our main SDK repository can trigger comprehensive tests in a separate automation repository. This architecture separates concerns effectively:

SDK Repository (Source Code)
     ↓ [Commit with trigger]
GitHub Actions Bridge
     ↓ [Cross-repo workflow dispatch]  
Automation Repository (Test Suite)
     ↓ [Execute comprehensive tests]
Results & Artifacts

Key Components

1. Smart Trigger Detection

# SDK Repository Workflow
on:
push:
branches: [main, develop]
pull_request:
types: [opened, synchronize]

jobs:
check-trigger:
runs-on: ubuntu-latest
steps:
- name: Check for test trigger
run: |
COMMIT_MESSAGE="$"
if [[ "$COMMIT_MESSAGE" == *"[run-test]"* ]]; then
echo "trigger=true" >> $GITHUB_OUTPUT
fi

2. Cross-Repository Communication

# Trigger automation tests in separate repo
- name: Trigger automation tests
if: steps.check.outputs.trigger == 'true'
run: |
# Clone automation repo
git clone https://$@github.com/org/automation-repo.git
cd automation-repo


# Create trigger commit with test parameters
git commit --allow-empty -m "Test SDK branch:$ [run-test]"
git push origin main

3. Dynamic Test Environment Configuration

// Automation Repository - Dynamic URL construction
const buildTestURL = (baseURL, params) => {
const { branch, version, accountId, region, token } = params;
const sdkParam = version ? `sdkVersion=${version}` : `sdkBranch=${branch}`;

return `${baseURL}?region=${region}&accountId=${accountId}&token=${token}&${sdkParam}`;
};

// Usage in tests
const testURL = buildTestURL('https://test-app.com/automation', {
branch: process.env.BRANCH || 'main',
version: process.env.VERSION,
accountId: 'test-account',
region: 'us1',
token: 'test-token'
});

Implementation Deep Dive

1. Test Suite Structure

We organized our tests into logical modules that mirror our SDK's architecture:

tests/
├── core/
│   ├── initialization.spec.js    # SDK bootstrap & config
│   ├── publicAPI.spec.js         # Core API methods
│   └── eventTracking.spec.js     # Analytics & events
├── features/
│   ├── webPopups.spec.js         # Modal & overlay features
│   ├── webInbox.spec.js          # In-app messaging
│   └── nativeDisplay.spec.js     # Native UI components
└── utils/
    ├── testHelpers.js            # Shared test utilities
    └── constants.js              # Environment configs

2. Playwright Configuration for SDK Testing

// playwright.config.js
module.exports = {
testDir: './tests',
timeout: 60000,
retries: 2,

use: {
baseURL: process.env.TEST_URL,
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'retain-on-failure'
},

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] }
}
],

reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'test-results.xml' }]
]
};

3. Smart Parameter Extraction

The automation system can parse different types of test triggers:

# Test specific branch
git commit -m "Feature update [run-test] branch:feature-xyz"

# Test specific version
git commit -m "Release prep [run-test] version:2.1.0"

# Test with multiple parameters
git commit -m "Hotfix [run-test] branch:hotfix-123 region:eu1"
# Parameter extraction logic
- name: Extract test parameters
run: |
COMMIT_MESSAGE="$"


# Extract branch
if [[ "$COMMIT_MESSAGE" =~ branch:([^ ]*) ]]; then
echo "BRANCH=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
fi

# Extract version
if [[ "$COMMIT_MESSAGE" =~ version:([^ ]*) ]]; then
echo "VERSION=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
fi

4. Comprehensive Test Coverage

Our test suite covers critical SDK functionality:

// Core SDK initialization test
test('SDK initializes with correct configuration', async ({ page }) => {
await page.goto(testURL);
await page.waitForFunction(() => window.clevertap !== undefined);

const isInitialized = await page.evaluate(() => {
return window.clevertap.isInitialized();
});

expect(isInitialized).toBe(true);
});

// Feature-specific test
test('Web popup displays correctly', async ({ page }) => {
await page.goto(testURL);
await setupSDK(page);

// Trigger popup
await page.evaluate(() => {
window.clevertap.displayPopup('campaign-id');
});

// Verify popup appearance
await expect(page.locator('[data-testid="web-popup"]')).toBeVisible();
await expect(page.locator('.popup-content')).toContainText('Expected Content');
});

Results & Impact

Metrics That Matter

  • Testing Time: Reduced from 2-3 days to 30 minutes
  • Test Coverage: Increased from ~40% to 95% of critical paths
  • Release Frequency: From monthly to weekly releases
  • Bug Escape Rate: Reduced by 90% in production

Process Transformation

Before (Manual Process)

Developer creates PR → Manual testing (2-3 days) → 
Bugs found → Fix bugs → Re-test manually → 
Release (with fingers crossed)

After (Automated Process)

Developer creates PR → Automated tests (30 mins) →
Results in GitHub → Fix if needed → Re-test automatically →
Confident release

Team Benefits

  • Developer Confidence: No more release anxiety
  • Faster Iteration: Quick feedback on feature branches
  • Better Documentation: Tests serve as living specifications
  • Team Morale: Engineers can focus on features, not manual testing

Key Technical Patterns

1. Separation of Concerns

  • SDK Repository: Focuses on code, lightweight workflow triggers
  • Automation Repository: Dedicated to comprehensive testing, reporting

2. Dynamic Environment Management

  • URL parameter-based configuration
  • Environment-specific test data
  • Branch/version specific testing

3. Robust Error Handling

// Retry logic for flaky network operations
const waitForSDKReady = async (page, retries = 3) => {
for (let i = 0; i < retries; i++) {
try {
await page.waitForFunction(() => window.sdk?.isReady, { timeout: 10000 });
return;
} catch (error) {
if (i === retries - 1) throw error;
await page.waitForTimeout(2000);
}
}
};

4. Comprehensive Reporting

  • HTML reports for developers
  • JUnit XML for CI integration
  • Screenshots and traces for debugging
  • Artifact retention for historical analysis

Lessons Learned

What Worked Well

  1. Clear trigger syntax makes it easy for developers to request tests
  2. Cross-repository architecture keeps concerns separated
  3. Dynamic configuration allows testing any branch/version combination
  4. Rich reporting makes debugging failures straightforward

Challenges We Overcame

  1. GitHub token permissions - Required careful scope management
  2. Test flakiness - Added proper waits and retry logic
  3. Environment synchronization - Used URL parameters for dynamic config
  4. Debugging failures - Comprehensive artifact collection was key

Implementation Guide

Phase 1: Basic Setup

  • [ ] Create separate automation repository
  • [ ] Set up GitHub Actions workflows
  • [ ] Configure cross-repository access tokens
  • [ ] Implement basic trigger detection

Phase 2: Test Development

  • [ ] Choose testing framework (Playwright, Cypress, etc.)
  • [ ] Create test environment configuration
  • [ ] Write core functionality tests
  • [ ] Add reporting and artifact collection

Phase 3: Enhancement

  • [ ] Add parameter parsing for branches/versions
  • [ ] Implement comprehensive error handling
  • [ ] Set up monitoring and notifications
  • [ ] Create documentation and runbooks

GitHub Actions Workflow Template

name: Cross-Repo Test Trigger

on:
push:
branches: [main]

jobs:
trigger-tests:
if: contains(github.event.head_commit.message, '[run-test]')
runs-on: ubuntu-latest
steps:
- name: Trigger automation
run: |
curl -X POST \
-H "Authorization: token $" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/your-org/automation-repo/actions/workflows/test.yml/dispatches \
-d '{"ref":"main","inputs":{"branch":"$"}}'

Test Structure Template

import { test, expect } from '@playwright/test';

test.describe('SDK Core Functionality', () => {
test.beforeEach(async ({ page }) => {
const testURL = buildTestURL(process.env.BASE_URL, {
branch: process.env.BRANCH || 'main',
version: process.env.VERSION
});

await page.goto(testURL);
await waitForSDKReady(page);
});

test('core initialization', async ({ page }) => {
// Your test logic here
});
});

Conclusion

Building this cross-repository automation pipeline transformed our release process from a source of stress into a competitive advantage. The key insight? Automation isn't just about saving time—it's about enabling confidence.

When you can test comprehensively and quickly, you ship faster, sleep better, and build better products. Start small, automate incrementally, and focus on the tests that matter most to your users.


Resources:

Recommendations

Understanding CRDTs: The Magic Behind Collaborative Editing

#CRDT

,

#Collaborative Editing

,

#Distributed Systems

,

#Real-time

,

#Tech Explained

A friendly deep dive into CRDTs and how they power real-time collaborative...

Oct 20, 2025

React Composition Patterns: Beyond Boolean Props

#React

,

#JavaScript

,

#Web Development

,

#Component Design

,

#Software Architecture

Learn how React compound components and composition patterns can help you escape...

Oct 6, 2025

10 Vue Debugging Tips That Will Transform Your Development Workflow

#Vue.js

,

#Debugging

,

#JavaScript

,

#Frontend

,

#DevTools

,

#Development

,

#Web Development

,

#Vue DevTools

Master Vue.js debugging with 10 battle-tested techniques from real developers....

Aug 26, 2025

JavaScript Performance Optimization, 10 Techniques That Actually Move the Needle

#javascript

,

#performance

Discover 10 JavaScript performance optimization techniques that deliver real,...

Aug 18, 2025

Building a Blog Publisher MCP Server to Automate Your Content Workflow with Claude

#MCP

,

#Claude

,

#Automation

,

#TypeScript

,

#GitHub

,

#Blogging

,

#Tutorial

,

#AI Tools

Learn how to build a custom MCP server that lets Claude publish and manage blog...

Aug 7, 2025

20 JavaScript Interview Questions You Should Know in 2025

A practical guide to 20 core JavaScript interview questions — with clear...

Jul 24, 2025

Building a Simple, Scalable Feature Flag System

#nextjs

,

#prisma

,

#feature-flags

,

#fullstack

,

#backend

,

#api-routes

,

#clean-architecture

,

#scalable-design

,

#product-rollout

Built a simple yet scalable feature flag system using Next.js API routes and...

Jul 6, 2025

I Refactored Without Changing a Feature — And It Broke Everything

#HyrumsLaw

,

#Refactoring

,

#LegacyCode

,

#CodeSmells

,

#TechDebt

,

#SoftwareEngineering

,

#CleanCode

Understanding Hyrum’s Law with a Real-World Lesson on Porting vs Refactoring

Jul 5, 2025

How to Publish Your First npm Package: Creating Rainbow Highlight with Utilities

#npm

,

#npm-package

,

#web

,

#javascript

Learn how to create and publish your first npm package. This step-by-step guide...

Sep 22, 2024

Google Dorking: Unlocking Hidden Search Capabilities & Insights

#seach

,

#seo

,

#research

Explore 16 advanced Google Dorking techniques to uncover valuable data, security...

Aug 8, 2024

This One HTML Attribute Could Save Your Web App from a Security Nightmare

#web-security

,

#cdn

,

#web

Web security is a critical concern for developers, yet some of the most...

Jun 29, 2024

Are You Still Using Basic CSS? Here Are 7 Tricks to Get Ahead of the Curve

#css

Bored of the same old CSS? Unleash 7 hidden gems to take your designs to the...

Dec 27, 2023

Easiest way to store your logs in a file WITHOUT chaging the source file(node)

#productivity

Often, developers face challenges when dealing with a flood of logs in the...

Dec 21, 2023

Build Your Own Pinterest-Style Masonry Grid: A Step-by-Step Guide

#css

,

#web

,

#layout

Create a masonary grid layout with left to right content flow, supporting...

Dec 10, 2023

Using git diff and git apply to Share Local Changes with Peers

#git

,

#productivity

,

#software_engeneering

,

#dev

git diff and git apply are two powerful Git commands that can be used to share...

Nov 12, 2023

React Portals: Render Components Outside the current DOM Hierarchy

#react

,

#web

The createPortal API in React allows you to render child elements into a...

Jul 27, 2023

Cloning Made Easy: Try degit and Clone Directories within Repos.

#git

,

#productivit

Have you ever faced the dilemma of wanting just a small portion of a repository,...

Jul 19, 2023

Debugging Web Apps with Browser Dev Tools: 6 Amazing Tricks

#browser

,

#debugging

,

#web

Debugging web applications can be a challenging task, with errors like...

Jul 13, 2023

Controlled Versus Uncontrolled Components in React

#react

,

#forms

Understanding State Management Within Forms Comparing controlled and...

Nov 5, 2022

Format Numbers, Dates and Currencies with the Intl Object in Javascript

#javascript

,

#html

,

#web

Intl object can be used to format data into commonly used formats of dates,...

Sep 13, 2022

Image Masking on Hover Using CSS Clip Path and Javascript

#javscript

,

#css

,

#html

Image Masking can be used to add fancy hover highlight effects to images for...

Jul 23, 2022

Recreating CSS Tricks Fancy Grid Hover Effect

#html

,

#css

,

#UI

,

#recreation

CSS Trick had a simple yet cool grid layout which I found dope. So lets try to...

May 21, 2022

File Explorer Recursive React Component

#react

,

#javascript

,

#web

How to create a recursive folder Component using react.

Apr 16, 2022

Add Google Fonts to Your React & NextJS + TailwindCSS Project (Next 14)

#css

,

#tailwindcss

,

#react

,

#nextjs

,

#tailwind

,

#design

Use Google Fonts in Your TailwindCSS Projects

Apr 6, 2022

Event Delegation in Javascript

#javscript

,

#css

,

#html

,

#web

,

#performance

Handling multiple Events in Javascript with minimal CPU Usage

Mar 6, 2022

A Simple Web Accessibility Trick that you most probably missed!

#html

,

#css

,

#web-accessibility

,

#user-experience

Imagine that you cannot use the mouse and have to Navigate a Website with the...

Dec 23, 2021

Top Terminal Commands I Use For Productivity

#linux

,

#cli

,

#terminal

The whole point of development is solving problems. But very often we Developers...

Nov 3, 2021

CSS Logical Properties

#css

,

#html

CSS logical properties are properties which are used to design element on the...

Oct 5, 2021

Fluid Typography in CSS 💧

#css

,

#html

,

#typography

CSS Best Practices in Fluid Typography

Aug 15, 2021

CSS Units in a Nutshell 🐚

#css

,

#html

Are you still writing your css units in pixels and percentages? if you are then...

Aug 8, 2021

Master Markdown in 5minutes ⌚

#markdown

,

#documentation

Markdown is a lightweight markup language for creating formatted text using a...

Aug 1, 2021

What is JAMStack ✨

#jamstack

Jamstack stands for Javascript APIS and Markup and it is based on this idea of...

Jul 31, 2021

+

Check my latest Blog Post

Understanding CRDTs: The Magic Behind Collaborative Editing

Read Now
Oh My Gawwdd!!!!!!!

Wow you have been viewing my site since 20 seconds!

+
+