Files
zeekay 26a9531f7b merge: upstream/main (sync, brand preserved)
# Conflicts:
#	.github/workflows/check-insights-major-version.yml
#	.github/workflows/check-posthog-major-version.yml
#	.github/workflows/ci-security.yaml
#	.github/workflows/validate-versioning.yml
#	AGENTS.md
#	AGENTS.md~upstream_main
#	packages/ai/src/otel/capture.ts
#	packages/ai/src/otel/mappers/aiSdk.ts
#	packages/ai/src/otel/mappers/index.ts
#	packages/ai/src/otel/types.ts
#	packages/core/src/__tests__/posthog.init.spec.ts
#	packages/react-native/src/PostHogProvider.tsx
#	packages/react/src/context/PostHogContext.ts
#	packages/react/src/context/PostHogProvider.tsx
#	tooling/eslint-plugin-insights/no-unsafe-web-global.js
2026-06-02 10:52:56 -07:00

185 lines
6.7 KiB
TypeScript

import { expect, test } from './utils/insights-playwright-test-base'
import { start } from './utils/setup'
import { Request } from '@playwright/test'
const startOptions = {
options: {},
url: '/playground/cypress/index.html',
}
test.describe('retry queue', () => {
test('retries failed capture requests and stops after success', async ({ page, context }) => {
test.setTimeout(90000)
const captureRequests: Request[] = []
let errorResponseCount = 0
const maxErrorResponses = 3
let successSeen = false
// Mock the capture endpoint to fail initially, then succeed
await context.route('**/e/**', async (route) => {
const request = route.request()
captureRequests.push(request)
if (errorResponseCount < maxErrorResponses) {
errorResponseCount++
await route.fulfill({
status: 500,
contentType: 'text/plain',
body: 'Internal Server Error',
})
} else {
successSeen = true
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'ok' }),
})
}
})
// Initialize Insights without pageview to avoid extra requests
await start({ ...startOptions, options: { capture_pageview: false } }, page, context)
// Capture a custom event which will initially fail
await page.evaluate(() => {
window.insights.capture('test-retry-event', { test: 'data' })
})
// Wait until we see the successful response (4th request)
await expect(async () => {
expect(successSeen).toBe(true)
}).toPass({ timeout: 50000 })
// Check that we got multiple requests
expect(captureRequests.length).toBeGreaterThanOrEqual(3)
// Verify the first request had no retry_count
const firstRequest = captureRequests[0]
expect(firstRequest.url()).not.toContain('retry_count')
// Verify retry_count increments
const retryCountMatches = captureRequests
.map((req) => {
const match = req.url().match(/retry_count=(\d+)/)
return match ? parseInt(match[1]) : null
})
.filter((count) => count !== null)
// Should see incrementing retry counts
expect(retryCountMatches.length).toBeGreaterThanOrEqual(2)
expect(retryCountMatches).toContain(1)
expect(retryCountMatches).toContain(2)
// Verify counts are actually incrementing (not stuck at 1)
const uniqueCounts = Array.from(new Set(retryCountMatches))
expect(uniqueCounts.length).toBeGreaterThan(1)
// After success, record the count and verify no more requests arrive
const requestCountAfterSuccess = captureRequests.length
await expect(async () => {
expect(captureRequests.length).toBe(requestCountAfterSuccess)
}).toPass({ timeout: 5000 })
})
test('stops retrying after 10 attempts', async ({ page, context }) => {
test.setTimeout(60000)
const captureRequests: Request[] = []
// Mock the capture endpoint to always fail
await context.route('**/e/**', async (route) => {
captureRequests.push(route.request())
await route.fulfill({
status: 500,
contentType: 'text/plain',
body: 'Internal Server Error',
})
})
// Initialize Insights without pageview
await start({ ...startOptions, options: { capture_pageview: false } }, page, context)
// Capture a custom event which will fail
await page.evaluate(() => {
window.insights.capture('test-max-retries-event', { test: 'data' })
})
// Wait for several retries
// The backoff increases exponentially: 3s, 6s, 12s, 24s, etc.
// We'll wait long enough to see at least 3-4 retries
await page.waitForTimeout(25000)
// Extract all retry counts
const retryCountMatches = captureRequests
.map((req) => {
const match = req.url().match(/retry_count=(\d+)/)
return match ? parseInt(match[1]) : null
})
.filter((count) => count !== null)
.sort((a, b) => a! - b!)
// Should have some retries but not exceed 10
expect(retryCountMatches.length).toBeGreaterThan(0)
const maxRetryCount = Math.max(...(retryCountMatches as number[]))
expect(maxRetryCount).toBeLessThanOrEqual(10)
// Verify counts are incrementing
expect(retryCountMatches).toContain(1)
expect(retryCountMatches).toContain(2)
})
test('immediately retries when coming back online', async ({ page, context }) => {
const captureRequests: Request[] = []
// Mock the capture endpoint to fail initially
let shouldSucceed = false
await context.route('**/e/**', async (route) => {
captureRequests.push(route.request())
if (shouldSucceed) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'ok' }),
})
} else {
await route.fulfill({
status: 500,
contentType: 'text/plain',
body: 'Internal Server Error',
})
}
})
// Initialize Insights without pageview
await start({ ...startOptions, options: { capture_pageview: false } }, page, context)
// Capture an event that will fail
await page.evaluate(() => {
window.insights.capture('test-offline-event', { test: 'data' })
})
// Wait for at least the initial request to be made
await expect(async () => {
expect(captureRequests.length).toBeGreaterThanOrEqual(1)
}).toPass({ timeout: 10000 })
// Simulate going offline
await context.setOffline(true)
// Record the count before going online
const requestsWhileOffline = captureRequests.length
// Switch to success mode and come back online
shouldSucceed = true
await context.setOffline(false)
// Trigger the online event
await page.evaluate(() => {
window.dispatchEvent(new Event('online'))
})
// Wait until we see at least one more request after coming online
await expect(async () => {
expect(captureRequests.length).toBeGreaterThan(requestsWhileOffline)
}).toPass({ timeout: 10000 })
})
})