Best for
- When configuring Karma for an Angular, React, or vanilla JavaScript project
- When setting up browser-based unit testing with Jasmine or Mocha
- When adding code coverage reporting to a Karma test suite
PramodDutta/qaskills/seed-skills/karma-testing/SKILL.md
Comprehensive Karma test runner skill for browser-based JavaScript unit testing with Jasmine, Mocha, or QUnit frameworks, real browser execution, coverage reporting, and CI/CD pipeline integration.
Decision brief
You are an expert QA engineer specializing in Karma test runner configuration and browser-based JavaScript testing. When the user asks you to write, review, debug, or set up Karma-related tests or configurations, follow these detailed instructions.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Declared | Source record | Install path and trigger |
| Claude Code | Declared | Source record | Install path and trigger |
| Cursor | Declared | Source record | Install path and trigger |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/karma-testing"Inspect the Agent Skill "Karma Testing" from https://github.com/PramodDutta/qaskills/blob/fb3fbec70591bad971dd97c5d9add6eaa99bae18/seed-skills/karma-testing/SKILL.md at commit fb3fbec70591bad971dd97c5d9add6eaa99bae18. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Review the “Setup” section in the pinned source before continuing.
1. Real Browser Execution -- Karma runs tests in actual browsers (Chrome, Firefox, Safari, Edge), catching browser-specific issues that Node.js-based runners miss. This is its primary advantage over purely Node-based test runners. 2. Framework Agnostic -- Karma works with Jasmin…
When configuring Karma for an Angular, React, or vanilla JavaScript project
Review the “Project Structure” section in the pinned source before continuing.
Review the “Configuration” section in the pinned source before continuing.
Permission review
The documentation includes network, browsing, or remote request actions.
apiClient = new ApiClient('http://localhost:3000/api');The documentation includes network, browsing, or remote request actions.
expect(window.fetch).toHaveBeenCalledWith('http://localhost:3000/api/users', jasmine.any(Object));The documentation asks the agent to run terminal commands or scripts.
npx karma startThe documentation asks the agent to run terminal commands or scripts.
npx karma start --single-runEvidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 93/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 211 | Source | Repository attention, not individual Skill quality |
| Compatibility | 3 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
You are an expert QA engineer specializing in Karma test runner configuration and browser-based JavaScript testing. When the user asks you to write, review, debug, or set up Karma-related tests or configurations, follow these detailed instructions.
Karma is a test runner that executes JavaScript tests in real browsers. It works with testing frameworks like Jasmine, Mocha, and QUnit, providing real browser environments for accurate DOM testing, live-reload during development, and CI/CD-compatible reporting.
files array in configuration determines which source and test files are loaded. Use glob patterns to include files systematically.autoWatch: true) re-runs tests on file changes, providing instant feedback during development.karma.conf.js, karma start, or Karma pluginsproject-root/
├── karma.conf.js # Karma configuration
├── karma.ci.conf.js # CI-specific overrides
├── src/
│ ├── components/
│ │ ├── calculator.js
│ │ ├── string-utils.js
│ │ └── form-validator.js
│ ├── services/
│ │ ├── api-client.js
│ │ └── storage.js
│ └── app.js
├── test/
│ ├── unit/
│ │ ├── components/
│ │ │ ├── calculator.spec.js
│ │ │ ├── string-utils.spec.js
│ │ │ └── form-validator.spec.js
│ │ └── services/
│ │ ├── api-client.spec.js
│ │ └── storage.spec.js
│ ├── helpers/
│ │ ├── test-setup.js
│ │ └── dom-helpers.js
│ └── fixtures/
│ └── mock-data.js
├── coverage/ # Generated coverage reports
└── package.json
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'src/**/*.js',
'test/helpers/**/*.js',
'test/unit/**/*.spec.js',
],
exclude: [],
preprocessors: {
'src/**/*.js': ['coverage'],
},
reporters: ['progress', 'coverage'],
coverageReporter: {
type: 'html',
dir: 'coverage/',
subdir: '.',
check: {
global: {
statements: 80,
branches: 75,
functions: 80,
lines: 80,
},
},
},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity,
browserNoActivityTimeout: 30000,
});
};
const baseConfig = require('./karma.conf.js');
module.exports = function (config) {
baseConfig(config);
config.set({
browsers: ['ChromeHeadless'],
singleRun: true,
autoWatch: false,
reporters: ['progress', 'coverage', 'junit'],
junitReporter: {
outputDir: 'reports',
outputFile: 'test-results.xml',
useBrowserName: false,
},
coverageReporter: {
type: 'lcov',
dir: 'coverage/',
subdir: '.',
check: {
global: {
statements: 80,
branches: 75,
functions: 80,
lines: 80,
},
},
},
});
};
const webpackConfig = require('./webpack.test.config');
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
{ pattern: 'test/**/*.spec.ts', watched: false },
],
preprocessors: {
'test/**/*.spec.ts': ['webpack', 'sourcemap'],
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only',
},
reporters: ['progress', 'coverage-istanbul'],
coverageIstanbulReporter: {
reports: ['html', 'lcovonly', 'text-summary'],
dir: 'coverage/',
fixWebpackSourcePaths: true,
thresholds: {
emitWarning: false,
global: {
statements: 80,
lines: 80,
branches: 75,
functions: 80,
},
},
},
browsers: ['ChromeHeadless'],
singleRun: true,
});
};
describe('Calculator', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe('add', () => {
it('should add two positive numbers', () => {
expect(calculator.add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(calculator.add(-1, -2)).toBe(-3);
});
it('should handle zero', () => {
expect(calculator.add(0, 5)).toBe(5);
expect(calculator.add(5, 0)).toBe(5);
});
it('should handle floating point numbers', () => {
expect(calculator.add(0.1, 0.2)).toBeCloseTo(0.3, 10);
});
});
describe('divide', () => {
it('should divide two numbers', () => {
expect(calculator.divide(10, 2)).toBe(5);
});
it('should throw on division by zero', () => {
expect(() => calculator.divide(10, 0)).toThrowError('Division by zero');
});
});
});
describe('StringUtils', () => {
describe('capitalize', () => {
it('should capitalize the first letter', () => {
expect(StringUtils.capitalize('hello')).toBe('Hello');
});
it('should handle empty strings', () => {
expect(StringUtils.capitalize('')).toBe('');
});
it('should handle already capitalized strings', () => {
expect(StringUtils.capitalize('Hello')).toBe('Hello');
});
it('should handle single characters', () => {
expect(StringUtils.capitalize('a')).toBe('A');
});
});
describe('truncate', () => {
it('should truncate long strings with ellipsis', () => {
const result = StringUtils.truncate('This is a very long string', 10);
expect(result).toBe('This is a ...');
expect(result.length).toBeLessThanOrEqual(13);
});
it('should not truncate short strings', () => {
expect(StringUtils.truncate('Short', 10)).toBe('Short');
});
});
});
describe('Form Validator', () => {
let container;
let validator;
beforeEach(() => {
container = document.createElement('div');
container.innerHTML = `
<form id="test-form">
<input type="text" id="name" data-testid="name-input" />
<input type="email" id="email" data-testid="email-input" />
<input type="password" id="password" data-testid="password-input" />
<span id="name-error" class="error" data-testid="name-error"></span>
<span id="email-error" class="error" data-testid="email-error"></span>
<button type="submit" data-testid="submit-btn">Submit</button>
</form>
`;
document.body.appendChild(container);
validator = new FormValidator('#test-form');
});
afterEach(() => {
document.body.removeChild(container);
});
it('should validate required name field', () => {
const nameInput = document.querySelector('[data-testid="name-input"]');
nameInput.value = '';
const result = validator.validateField('name');
expect(result.valid).toBe(false);
expect(result.message).toBe('Name is required');
});
it('should validate email format', () => {
const emailInput = document.querySelector('[data-testid="email-input"]');
emailInput.value = 'not-an-email';
const result = validator.validateField('email');
expect(result.valid).toBe(false);
expect(result.message).toContain('valid email');
});
it('should show error messages in DOM', () => {
const nameInput = document.querySelector('[data-testid="name-input"]');
nameInput.value = '';
validator.validate();
const errorEl = document.querySelector('[data-testid="name-error"]');
expect(errorEl.textContent).toBe('Name is required');
expect(errorEl.classList.contains('visible')).toBe(true);
});
it('should enable submit on valid form', () => {
document.querySelector('[data-testid="name-input"]').value = 'John';
document.querySelector('[data-testid="email-input"]').value = '[email protected]';
document.querySelector('[data-testid="password-input"]').value = 'SecurePass123!';
validator.validate();
const submitBtn = document.querySelector('[data-testid="submit-btn"]');
expect(submitBtn.disabled).toBe(false);
});
});
describe('ApiClient', () => {
let apiClient;
beforeEach(() => {
apiClient = new ApiClient('http://localhost:3000/api');
});
it('should fetch users successfully', async () => {
spyOn(window, 'fetch').and.returnValue(
Promise.resolve(
new Response(JSON.stringify([{ id: 1, name: 'Alice' }]), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
)
);
const users = await apiClient.getUsers();
expect(users).toEqual([{ id: 1, name: 'Alice' }]);
expect(window.fetch).toHaveBeenCalledWith('http://localhost:3000/api/users', jasmine.any(Object));
});
it('should handle API errors', async () => {
spyOn(window, 'fetch').and.returnValue(
Promise.resolve(new Response('Not Found', { status: 404 }))
);
try {
await apiClient.getUsers();
fail('Expected an error to be thrown');
} catch (error) {
expect(error.message).toContain('404');
}
});
it('should handle network failures', async () => {
spyOn(window, 'fetch').and.returnValue(Promise.reject(new Error('Network error')));
try {
await apiClient.getUsers();
fail('Expected an error to be thrown');
} catch (error) {
expect(error.message).toBe('Network error');
}
});
});
describe('EventTracker', () => {
let tracker;
let mockStorage;
beforeEach(() => {
mockStorage = jasmine.createSpyObj('Storage', ['getItem', 'setItem', 'removeItem']);
tracker = new EventTracker(mockStorage);
});
it('should store events in storage', () => {
tracker.track('page_view', { page: '/home' });
expect(mockStorage.setItem).toHaveBeenCalledWith(
jasmine.stringMatching(/^event_/),
jasmine.stringContaining('"type":"page_view"')
);
});
it('should retrieve event count', () => {
mockStorage.getItem.and.returnValue(JSON.stringify({ count: 5 }));
const count = tracker.getEventCount('page_view');
expect(count).toBe(5);
});
it('should call flush callback after batch size', () => {
const flushCallback = jasmine.createSpy('flushCallback');
tracker.onFlush(flushCallback);
for (let i = 0; i < 10; i++) {
tracker.track('click', { element: `btn_${i}` });
}
expect(flushCallback).toHaveBeenCalledTimes(1);
expect(flushCallback).toHaveBeenCalledWith(jasmine.arrayContaining([
jasmine.objectContaining({ type: 'click' }),
]));
});
});
describe('StorageService', () => {
let service;
beforeEach(() => {
localStorage.clear();
sessionStorage.clear();
service = new StorageService();
});
afterEach(() => {
localStorage.clear();
sessionStorage.clear();
});
it('should store and retrieve values', () => {
service.set('user', { name: 'Alice', role: 'admin' });
const result = service.get('user');
expect(result).toEqual({ name: 'Alice', role: 'admin' });
});
it('should return null for missing keys', () => {
expect(service.get('nonexistent')).toBeNull();
});
it('should handle storage quota exceeded', () => {
spyOn(localStorage, 'setItem').and.throwError('QuotaExceededError');
expect(() => service.set('key', 'value')).toThrowError('Storage quota exceeded');
});
it('should clear expired items', () => {
service.set('temp', 'data', { ttl: -1 }); // Already expired
service.cleanExpired();
expect(service.get('temp')).toBeNull();
});
});
ChromeHeadless or FirefoxHeadless for CI pipelines to avoid display server requirements while maintaining real browser engine testing.coverageReporter.check.global and fail the build when coverage drops below acceptable levels.singleRun: true for CI -- Ensure Karma exits after tests complete in CI environments. Watch mode (autoWatch: true) is for development only.document.body in afterEach hooks to prevent test pollution.createSpyObj for creating mock objects with multiple methods. This is cleaner than manually stubbing each method.browserNoActivityTimeout and browserDisconnectTimeout high enough for slow CI environments but low enough to catch hanging tests.karma.ci.conf.js that extends the base config with CI-specific settings (headless, single run, junit reporter).describe blocks -- Organize tests hierarchically by module, class, and method for clear reporting output.document.body in tests persist across tests, causing side effects and false positives.singleRun in CI -- Without singleRun: true, Karma watches for changes and never exits, hanging the CI pipeline.files array -- Use glob patterns (src/**/*.js) instead of listing individual files. New files are automatically included.fit and fdescribe in committed code -- Focused tests (fit, fdescribe) skip other tests silently. Use --grep for selective execution instead.browserNoActivityTimeout too low -- Tests that involve async operations may need more than the default timeout. Set it to at least 30 seconds for CI.progress reporter is insufficient for CI. Add junit for CI integration and coverage for quality metrics.# Run tests (uses karma.conf.js by default)
npx karma start
# Run in single-run mode
npx karma start --single-run
# Run with specific config
npx karma start karma.ci.conf.js
# Run with specific browsers
npx karma start --browsers ChromeHeadless,FirefoxHeadless
# Run specific test files
npx karma start --files "test/unit/calculator.spec.js"
# Run with verbose logging
npx karma start --log-level debug
# Initialize karma config
npx karma init
# Watch and re-run on changes
npx karma start --auto-watch --no-single-run
# Install Karma and Jasmine
npm install --save-dev karma karma-jasmine karma-chrome-launcher jasmine-core
# Coverage reporting
npm install --save-dev karma-coverage
# CI reporting
npm install --save-dev karma-junit-reporter
# TypeScript support
npm install --save-dev karma-webpack karma-sourcemap-loader typescript ts-loader
# Istanbul coverage for webpack/TypeScript
npm install --save-dev karma-coverage-istanbul-reporter
# Initialize configuration
npx karma init karma.conf.js
Frequently asked questions
You are an expert QA engineer specializing in Karma test runner configuration and browser-based JavaScript testing. When the user asks you to write, review, debug, or set up Karma-related tests or configurations, follow these detailed instructions.
The source record exposes this install command: npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/karma-testing". Inspect the command and pinned source before running it.
The pinned source record declares support for: codex, claude code, cursor.
Static rules flagged network, exec-script in the source; the page lists the matching lines and excerpts.
Alternatives
PramodDutta/qaskills
Optimize resumes for Applicant Tracking Systems, check ATS compatibility, and analyze keyword match
PramodDutta/qaskills
Generate optimized test combinations using pairwise (all-pairs) testing algorithms to achieve maximum coverage with minimum test cases across multiple input parameters
PramodDutta/qaskills
Gate RAG pipelines in CI with versioned golden eval sets, per-metric thresholds, baseline drift detection, and a build that fails when retrieval or answer quality regresses.
PramodDutta/qaskills
Generate comprehensive test cases from state machine models covering all states, transitions, guard conditions, and invalid transition attempts for workflow-heavy features