Claude-code-plugins-plus-skills serpapi-local-dev-loop
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/serpapi-pack/skills/serpapi-local-dev-loop" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-serpapi-local-dev-loop && rm -rf "$T"
manifest:
plugins/saas-packs/serpapi-pack/skills/serpapi-local-dev-loop/SKILL.mdsource content
SerpApi Local Dev Loop
Overview
Set up local development for SerpApi with response caching, fixture recording, and offline testing. SerpApi charges per search, so caching results locally is critical for cost-effective development.
Instructions
Step 1: Record Real Responses as Fixtures
import serpapi, json, os, hashlib def record_fixture(params: dict, fixtures_dir="tests/fixtures"): """Run a real search and save the response as a fixture file.""" os.makedirs(fixtures_dir, exist_ok=True) client = serpapi.Client(api_key=os.environ["SERPAPI_API_KEY"]) result = client.search(**params) # Deterministic filename from params key = hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest()[:12] path = os.path.join(fixtures_dir, f"{params['engine']}_{key}.json") with open(path, "w") as f: json.dump(dict(result), f, indent=2) print(f"Recorded: {path}") # Record fixtures for common queries record_fixture({"engine": "google", "q": "python tutorial", "num": 5}) record_fixture({"engine": "youtube", "search_query": "react hooks"}) record_fixture({"engine": "bing", "q": "machine learning"})
Step 2: Mock Client for Testing
import json, os class MockSerpApiClient: def __init__(self, fixtures_dir="tests/fixtures"): self.fixtures_dir = fixtures_dir def search(self, **params): key = hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest()[:12] path = os.path.join(self.fixtures_dir, f"{params['engine']}_{key}.json") if os.path.exists(path): with open(path) as f: return json.load(f) raise FileNotFoundError(f"No fixture for {params}. Run record_fixture() first.")
Step 3: Vitest Mocking (Node.js)
// tests/serpapi.test.ts import { describe, it, expect, vi } from 'vitest'; import { readFileSync } from 'fs'; vi.mock('serpapi', () => ({ getJson: vi.fn(async (params) => { const fixture = JSON.parse( readFileSync(`tests/fixtures/google_sample.json`, 'utf-8') ); return fixture; }), })); describe('Search Service', () => { it('parses organic results', async () => { const { getJson } = await import('serpapi'); const result = await getJson({ engine: 'google', q: 'test' }); expect(result.organic_results).toBeDefined(); expect(result.organic_results[0]).toHaveProperty('title'); expect(result.organic_results[0]).toHaveProperty('link'); }); });
Step 4: Environment Separation
# .env.development (uses real API, low num for cost) SERPAPI_API_KEY=real-key-here SERPAPI_DEFAULT_NUM=3 # .env.test (uses fixtures, no API calls) SERPAPI_API_KEY=not-needed SERPAPI_USE_FIXTURES=true
Error Handling
| Error | Cause | Solution |
|---|---|---|
fixture | Missing fixture | Run with real API key |
| Stale fixtures | Search results changed | Re-record periodically |
in dev | Env not loaded | Check loading |
Resources
Next Steps
Proceed to
serpapi-sdk-patterns for production patterns.