pramoddutta/qaskills/race-condition-finder

Trust tier 2
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Detect and diagnose race conditions in web applications through concurrent interaction testing, state mutation analysis, and timing-sensitive scenario reproduction.

Install

npx vskill@latest install pramoddutta/qaskills/race-condition-finder

SKILL.md

Race Condition Finder Skill

You are an expert QA automation engineer specializing in detecting, diagnosing, and preventing race conditions in web applications. When the user asks you to find race conditions, test concurrent interactions, or diagnose timing-sensitive bugs, follow these detailed instructions.

Core Principles

  1. Race conditions are deterministic in cause but non-deterministic in manifestation -- A race condition exists because of a concrete code flaw (missing lock, unsynchronized state update, absent queue), but it manifests unpredictably. Your tests must make the non-deterministic deterministic by controlling timing and concurrency.
  1. Concurrency bugs hide behind success -- Most test runs pass even when race conditions exist because the default execution order happens to be safe. You must deliberately create adversarial timing conditions to surface these bugs.
  1. State mutations are the root cause -- Every race condition involves at least two operations competing to read or write shared state. Identify the shared state first, then construct tests that exercise concurrent access paths against it.
  1. Network latency is an amplifier, not the cause -- Slow network responses do not create race conditions; they reveal race conditions that already exist in the application logic. Simulate variable latency to expose these hidden flaws.
  1. Reproduce before you fix -- A race condition that cannot be reliably reproduced in a test will return after the fix. Invest in building a deterministic reproduction before suggesting code changes.
  1. Parallel user actions are the most common trigger -- Double-clicks, rapid navigation, concurrent form submissions, and multiple tabs operating on the same resource represent the highest-risk scenarios in web applications.
  1. Test the boundaries between client and server state -- The most dangerous race conditions occur where optimistic UI updates diverge from actual server state. Test that eventual consistency is actually achieved.

Project Structure

Organize your race condition testing suite with this directory structure:

tests/
race-conditions/
double-submit.spec.ts
rapid-navigation.spec.ts
concurrent-api-calls.spec.ts
optimistic-update-conflicts.spec.ts
multi-tab-state.spec.ts
stale-closure.spec.ts
fixtures/
race-condition.fixture.ts
helpers/
timing-controller.ts
request-interceptor.ts
state-snapshot.ts
concurrency-utils.ts
reports/
race-condition-report.json
playwright.config.ts

Timing Controller

The foundation of race condition testing is the ability to control when asynchronous operations resolve. Build a timing controller that intercepts network requests and holds them until you explicitly release them.

Request Interception and Delayed Resolution
typescript
import { Page, Route } from '@playwright/test';
interface PendingRequest {
route: Route;
url: string;
method: string;
timestamp: number;
resolve: () => Promise<void>;
}
export class TimingController {
private pendingRequests: Map<string, PendingRequest[]> = new Map();
private page: Page;
constructor(page: Page) {
this.page = page;
}
async interceptRoute(urlPattern: string | RegExp): Promise<void> {
await this.page.route(urlPattern, async (route) => {
const key = this.getRouteKey(route);
const pending: PendingRequest = {
route,
url: route.request().url(),
method: route.request().method(),
timestamp: Date.now(),
resolve: async () => {
await route.continue();
},
};
if (!this.pendingRequests.has(key)) {
this.pendingRequests.set(key, []);
}
this.pendingRequests.get(key)!.push(pending);
});
}
async holdAndRelease(
urlPattern: string,
action: () => Promise<void>,
delayMs: number = 0
): Promise<void> {
await this.interceptRoute(urlPattern);
await action();
if (delayMs > 0) {
await this.page.waitForTimeout(delayMs);
}
await this.releaseAll(urlPattern);
}
async releaseAll(key?: string): Promise<void> {
if (key) {
const pending = this.pendingRequests.get(key) || [];
for (const req of pending) {
await req.resolve();
}
this.pendingRequests.delete(key);
} else {
for (const [, requests] of this.pendingRequests) {
for (const req of requests) {
await req.resolve();
}
}
this.pendingRequests.clear();
}
}
async releaseInOrder(key: string, indices: number[]): Promise<void> {
const pending = this.pendingRequests.get(key) || [];
for (const index of indices) {
if (pending[index]) {
await pending[index].resolve();
}
}
}
async releaseInReverseOrder(key: string): Promise<void> {
const pending = this.pendingRequests.get(key) || [];
for (let i = pending.length - 1; i >= 0; i--) {
await pending[i].resolve();
}
this.pendingRequests.delete(key);
}
getPendingCount(key?: string): number {
if (key) {
return (this.pendingRequests.get(key) || []).length;
}
let total = 0;
for (const [, requests] of this.pendingRequests) {
total += requests.length;
}
return total;
}
private getRouteKey(route: Route): string {
const url = new URL(route.request().url());
return `${route.request().method()}:${url.pathname}`;
}
}
State Snapshot Utility

Capture application state at multiple points in time to detect inconsistencies caused by race conditions.

typescript
import { Page } from '@playwright/test';
interface StateSnapshot {
timestamp: number;
label: string;
domState: Record<string, string>;
localStorageState: Record<string, string>;
networkPending: number;
consoleErrors: string[];
}
export class StateSnapshotManager {
private snapshots: StateSnapshot[] = [];
private consoleErrors: string[] = [];
private page: Page;
constructor(page: Page) {
this.page = page;
this.page.on('console', (msg) => {
if (msg.type() === 'error') {
this.consoleErrors.push(msg.text());
}
});
}
async capture(label: string, selectors: Record<string, string>): Promise<StateSnapshot> {
const domState: Record<string, string> = {};
for (const [key, selector] of Object.entries(selectors)) {
try {
const element = this.page.locator(selector);
domState[key] = await element.textContent() || '';
} catch {
domState[key] = '__NOT_FOUND__';
}
}
const localStorageState = await this.page.evaluate(() => {
const state: Record<string, string> = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) {
state[key] = localStorage.getItem(key) || '';
}
}
return state;
});
const snapshot: StateSnapshot = {
timestamp: Date.now(),
label,
domState,
localStorageState,
networkPending: 0,
consoleErrors: [...this.consoleErrors],
};
this.snapshots.push(snapshot);
return snapshot;
}
compareSnapshots(
labelA: string,
labelB: string
): Record<string, { before: string; after: string }> {
const a = this.snapshots.find((s) => s.label === labelA);
const b = this.snapshots.find((s) => s.label === labelB);
if (!a || !b) throw new Error(`Snapshot not found: ${!a ? labelA : labelB}`);
const diffs: Record<string, { before: string; after: string }> = {};
const allKeys = new Set([...Object.keys(a.domState), ...Object.keys(b.domState)]);
for (const key of allKeys) {
if (a.domState[key] !== b.domState[key]) {
diffs[key] = { before: a.domState[key] || '', after: b.domState[key] || '' };
}
}
return diffs;
}
getSnapshots(): StateSnapshot[] {
return [...this.snapshots];
}
clear(): void {
this.snapshots = [];
this.consoleErrors = [];
}
}

Detailed Testing Guides

1. Double-Submit Prevention Testing

The most common race condition in web applications is the double-submit problem: a user clicks a submit button twice before the first request completes, creating duplicate records or inconsistent state.

typescript
import { test, expect } from '@playwright/test';
import { TimingController } from '../helpers/timing-controller';
test.describe('Double Submit Prevention', () => {
test('rapid double-click on submit button should not create duplicate entries', async ({
page,
}) => {
const timing = new TimingController(page);
await page.goto('/orders/new');
await page.fill('#product-name', 'Test Product');
await page.fill('#quantity', '1');
let apiCallCount = 0;
await page.route('**/api/orders', async (route) => {
apiCallCount++;
await new Promise((resolve) => setTimeout(resolve, 2000));
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ id: `order-${apiCallCount}`, status: 'created' }),
});
});
const submitButton = page.locator('button[type="submit"]');
await submitButton.dblclick();
await page.waitForTimeout(3000);
expect(apiCallCount).toBe(1);
await expect(submitButton).toBeDisabled();
});
test('clicking submit during pending request should show loading state', async ({ page }) => {
await page.goto('/orders/new');
await page.fill('#product-name', 'Test Product');
const requestPromises: (() => void)[] = [];
await page.route('**/api/orders', async (route) => {
await new Promise<void>((resolve) => {
requestPromises.push(resolve);
});
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ id: 'order-1', status: 'created' }),
});
});
const submitButton = page.locator('button[type="submit"]');
await submitButton.click();
await expect(submitButton).toHaveAttribute('aria-busy', 'true');
await expect(page.locator('.loading-spinner')).toBeVisible();
await submitButton.click({ force: true });
expect(requestPromises.length).toBe(1);
requestPromises[0]();
await expect(page.locator('.success-message')).toBeVisible();
});
test('form submission via Enter key during pending request should be ignored', async ({
page,
}) => {
await page.goto('/orders/new');
let apiCallCount = 0;
await page.route('**/api/orders', async (route) => {
apiCallCount++;
await new Promise((resolve) => setTimeout(resolve, 1500));
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ id: `order-${apiCallCount}` }),
});
});
await page.fill('#product-name', 'Test Product');
await page.press('#product-name', 'Enter');
await page.press('#product-name', 'Enter');
await page.press('#product-name', 'Enter');
await page.waitForTimeout(2000);
expect(apiCallCount).toBe(1);
});
});
2. Rapid Navigation Race Conditions

When users navigate quickly between pages, previous page data fetches may resolve after the new page has rendered, overwriting the current view with stale data.

typescript
import { test, expect } from '@playwright/test';
test.describe('Rapid Navigation Race Conditions', () => {
test('fast navigation should display data for the final destination only', async ({ page }) => {
const responses: { url: string; resolveTime: number }[] = [];
await page.route('**/api/products/*', async (route) => {
const url = route.request().url();
const id = url.split('/').pop();
const delay = id === '1' ? 3000 : id === '2' ? 2000 : 500;
responses.push({ url, resolveTime: delay });

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
118
Repository forks
6
7-day trend score
24

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 13 May 2026, 5 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/pramoddutta/qaskills/race-condition-finder/badge)](https://verified-skill.com/skills/pramoddutta/qaskills/race-condition-finder)

Something wrong with this skill or its page? Tell us.