my favourite takeaway has this order now not button in their hero image
-
it's a great example of why you need dead click tracking
+
it's a great example of why you need dead click tracking
diff --git a/playground/cypress/index.html b/playground/cypress/index.html
index e3340f48e..0ed6f036c 100644
--- a/playground/cypress/index.html
+++ b/playground/cypress/index.html
@@ -90,7 +90,7 @@
my favourite takeaway has this order now not button in their hero image
-
it's a great example of why you need dead click tracking
+
it's a great example of why you need dead click tracking
diff --git a/playwright/autocapture-config.spec.ts b/playwright/autocapture-config.spec.ts
new file mode 100644
index 000000000..499f8373f
--- /dev/null
+++ b/playwright/autocapture-config.spec.ts
@@ -0,0 +1,145 @@
+import { test } from './utils/posthog-playwright-test-base'
+import { start } from './utils/setup'
+
+const startOptions = {
+ options: {},
+ decideResponseOverrides: {
+ sessionRecording: {
+ endpoint: '/ses/',
+ },
+ capturePerformance: true,
+ },
+ url: './playground/cypress/index.html',
+}
+
+test.describe('autocapture config', () => {
+ test('do not capture click if not in allowlist', async ({ page, context }) => {
+ await start(
+ {
+ ...startOptions,
+ options: {
+ ...startOptions.options,
+ capture_pageview: false,
+ autocapture: {
+ dom_event_allowlist: ['change'],
+ },
+ },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ // no autocapture event from click
+ await page.expectCapturedEventsToBe(['custom-event'])
+
+ await page.locator('[data-cy-input]').fill('hello posthog!')
+ // blur the input
+ await page.locator('body').click()
+ await page.expectCapturedEventsToBe(['custom-event', '$autocapture'])
+ })
+
+ test('capture clicks when configured to', async ({ page, context }) => {
+ await start(
+ {
+ ...startOptions,
+ options: { ...startOptions.options, autocapture: { dom_event_allowlist: ['click'] } },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.expectCapturedEventsToBe(['$pageview', '$autocapture', 'custom-event'])
+
+ await page.locator('[data-cy-input]').fill('hello posthog!')
+ // blur the input
+ await page.locator('body').click()
+ // no change autocapture event
+ await page.expectCapturedEventsToBe(['$pageview', '$autocapture', 'custom-event'])
+ })
+
+ test('obeys url allowlist', async ({ page, context }) => {
+ await start(
+ {
+ ...startOptions,
+ options: { ...startOptions.options, autocapture: { url_allowlist: ['.*test-is-not-on-this.*'] } },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.expectCapturedEventsToBe(['$pageview', 'custom-event'])
+
+ await page.resetCapturedEvents()
+ await start(
+ {
+ ...startOptions,
+ options: { ...startOptions.options, autocapture: { url_allowlist: ['.*cypress.*'] } },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.expectCapturedEventsToBe(['$pageview', '$autocapture', 'custom-event'])
+ })
+
+ test('obeys element allowlist', async ({ page, context }) => {
+ await start(
+ {
+ ...startOptions,
+ options: { ...startOptions.options, autocapture: { element_allowlist: ['button'] } },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.expectCapturedEventsToBe(['$pageview', '$autocapture', 'custom-event'])
+
+ await page.resetCapturedEvents()
+ await start(
+ {
+ ...startOptions,
+ options: { ...startOptions.options, autocapture: { element_allowlist: ['input'] } },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.expectCapturedEventsToBe(['$pageview', 'custom-event'])
+ })
+
+ test('obeys css selector allowlist', async ({ page, context }) => {
+ await start(
+ {
+ ...startOptions,
+ options: {
+ ...startOptions.options,
+ autocapture: { css_selector_allowlist: ['[data-cy-custom-event-button]'] },
+ },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.expectCapturedEventsToBe(['$pageview', '$autocapture', 'custom-event'])
+
+ await page.resetCapturedEvents()
+ await start(
+ {
+ ...startOptions,
+ options: { ...startOptions.options, autocapture: { css_selector_allowlist: ['[data-cy-input]'] } },
+ },
+ page,
+ context
+ )
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.expectCapturedEventsToBe(['$pageview', 'custom-event'])
+ })
+})
diff --git a/playwright/before_send.spec.ts b/playwright/before_send.spec.ts
new file mode 100644
index 000000000..26a5e35ea
--- /dev/null
+++ b/playwright/before_send.spec.ts
@@ -0,0 +1,76 @@
+import { expect, test, WindowWithPostHog } from './utils/posthog-playwright-test-base'
+import { start } from './utils/setup'
+import { BeforeSendFn } from '../src/types'
+
+const startOptions = {
+ options: {
+ session_recording: {},
+ },
+ decideResponseOverrides: {
+ sessionRecording: {
+ endpoint: '/ses/',
+ },
+ capturePerformance: true,
+ },
+ url: './playground/cypress-full/index.html',
+}
+
+test.describe('before_send', () => {
+ test('can sample and edit with before_send', async ({ page, context }) => {
+ await start(startOptions, page, context)
+
+ await page.evaluate(() => {
+ const posthog = (window as WindowWithPostHog).posthog
+ if (!posthog) {
+ throw new Error('PostHog is not initialized')
+ }
+ let counter = 0
+ // box the original before_send function
+ const og: BeforeSendFn[] = Array.isArray(posthog.config.before_send)
+ ? posthog.config.before_send
+ : posthog.config.before_send !== undefined
+ ? [posthog.config.before_send]
+ : []
+
+ posthog.config.before_send = [
+ (cr) => {
+ if (!cr) {
+ return null
+ }
+
+ if (cr.event === 'custom-event') {
+ counter++
+ if (counter === 2) {
+ return null
+ }
+ }
+ if (cr.event === '$autocapture') {
+ return {
+ ...cr,
+ event: 'redacted',
+ }
+ }
+ return cr
+ },
+ // these tests rely on existing before_send function to capture events
+ // so we have to add it back in here
+ ...og,
+ ]
+ })
+
+ await page.locator('[data-cy-custom-event-button]').click()
+ await page.locator('[data-cy-custom-event-button]').click()
+
+ const captures = (await page.capturedEvents()).map((x) => x.event)
+
+ expect(captures).toEqual([
+ // before adding the new before sendfn
+ '$pageview',
+ 'redacted',
+ 'custom-event',
+ // second button click only has the redacted autocapture event
+ 'redacted',
+ // because the second custom-event is rejected
+ ])
+ })
+})
diff --git a/playwright/capture.spec.ts b/playwright/capture.spec.ts
new file mode 100644
index 000000000..5f3192cd0
--- /dev/null
+++ b/playwright/capture.spec.ts
@@ -0,0 +1,213 @@
+import { expect, test } from './utils/posthog-playwright-test-base'
+import { start } from './utils/setup'
+import { pollUntilEventCaptured } from './utils/event-capture-utils'
+import { Request } from '@playwright/test'
+import { decompressSync, strFromU8 } from 'fflate'
+
+function getGzipEncodedPayloady(req: Request): Record