playwright-posthog 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/global.d.ts +77 -0
- package/dist/hog-watcher.d.ts +6 -0
- package/dist/hog-watcher.d.ts.map +1 -0
- package/dist/hog-watcher.js +112 -0
- package/dist/hog-watcher.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/matchers.d.ts +24 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/matchers.js +101 -0
- package/dist/matchers.js.map +1 -0
- package/dist/symbols.d.ts +10 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +10 -0
- package/dist/symbols.js.map +1 -0
- package/examples/example.spec.ts +126 -0
- package/examples/playwright.config.ts +69 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 playwright-posthog contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# 🦔 playwright-posthog
|
|
2
|
+
|
|
3
|
+
Playwright matchers for testing PostHog analytics events.
|
|
4
|
+
|
|
5
|
+
## 📦 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev playwright-posthog
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 🚀 Setup
|
|
12
|
+
|
|
13
|
+
Extend your Playwright test with PostHog tracking:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// fixtures.ts
|
|
17
|
+
import { test as base, expect as baseExpect } from '@playwright/test';
|
|
18
|
+
import { withPostHogTracking, matchers } from 'playwright-posthog';
|
|
19
|
+
|
|
20
|
+
export const test = withPostHogTracking(base);
|
|
21
|
+
export const expect = baseExpect.extend(matchers);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then use in your tests:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// my-test.spec.ts
|
|
28
|
+
import { test, expect } from './fixtures';
|
|
29
|
+
|
|
30
|
+
test('user signup tracking works', async ({ page }) => {
|
|
31
|
+
await page.goto('/signup');
|
|
32
|
+
await page.getByLabel('Email').fill('user@example.com');
|
|
33
|
+
await page.getByText('Sign Up').click();
|
|
34
|
+
|
|
35
|
+
// ✅ Assert the event was fired
|
|
36
|
+
await expect(page).toHaveFiredEvent('user_signed_up', {
|
|
37
|
+
plan: 'pro',
|
|
38
|
+
source: 'web'
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 📖 API Reference
|
|
44
|
+
|
|
45
|
+
### `withPostHogTracking(test)`
|
|
46
|
+
|
|
47
|
+
Extends a Playwright test instance with PostHog event tracking on the `page` fixture.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { test as base } from '@playwright/test';
|
|
51
|
+
import { withPostHogTracking } from 'playwright-posthog';
|
|
52
|
+
|
|
53
|
+
export const test = withPostHogTracking(base);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Works with already-extended tests too:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const testWithAuth = base.extend({
|
|
60
|
+
authenticatedPage: async ({ page }, use) => {
|
|
61
|
+
// ... login logic
|
|
62
|
+
await use(page);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const test = withPostHogTracking(testWithAuth);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `matchers`
|
|
70
|
+
|
|
71
|
+
Custom matchers to extend Playwright's `expect`:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { expect as baseExpect } from '@playwright/test';
|
|
75
|
+
import { matchers } from 'playwright-posthog';
|
|
76
|
+
|
|
77
|
+
export const expect = baseExpect.extend(matchers);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 🎯 Matchers
|
|
81
|
+
|
|
82
|
+
#### `toHaveFiredEvent(eventName, properties?, config?)`
|
|
83
|
+
|
|
84
|
+
Asserts that a PostHog event was fired. Automatically polls for up to 2 seconds.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Basic usage
|
|
88
|
+
await expect(page).toHaveFiredEvent('button_clicked');
|
|
89
|
+
|
|
90
|
+
// With property matching (subset match)
|
|
91
|
+
await expect(page).toHaveFiredEvent('purchase_completed', {
|
|
92
|
+
plan: 'pro',
|
|
93
|
+
amount: 99.99
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// With custom timeout
|
|
97
|
+
await expect(page).toHaveFiredEvent('slow_event', {}, {
|
|
98
|
+
timeout: 5000, // Wait up to 5 seconds
|
|
99
|
+
pollInterval: 200 // Check every 200ms
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> 💡 **Properties Matching**: Uses subset matching - the event properties must *contain* the expected properties but can have additional ones.
|
|
104
|
+
|
|
105
|
+
#### `notToHaveFiredEvent(eventName, properties?, config?)`
|
|
106
|
+
|
|
107
|
+
Asserts that an event was NOT fired:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
await expect(page).notToHaveFiredEvent('error_occurred');
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### `toHaveCapturedEvents(count?)`
|
|
114
|
+
|
|
115
|
+
Asserts that events were captured:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// At least one event
|
|
119
|
+
await expect(page).toHaveCapturedEvents();
|
|
120
|
+
|
|
121
|
+
// Exactly 5 events
|
|
122
|
+
await expect(page).toHaveCapturedEvents(5);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 🐛 Debug Mode
|
|
126
|
+
|
|
127
|
+
Enable debug mode via the `DEBUG` environment variable:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
DEBUG=true npx playwright test
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 🛠️ Utility Functions
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { getCapturedEvents, clearCapturedEvents } from 'playwright-posthog';
|
|
137
|
+
|
|
138
|
+
test('advanced usage', async ({ page }) => {
|
|
139
|
+
await page.goto('/');
|
|
140
|
+
|
|
141
|
+
// Get all captured events
|
|
142
|
+
const events = getCapturedEvents(page);
|
|
143
|
+
|
|
144
|
+
// Clear events (useful for multi-step tests)
|
|
145
|
+
clearCapturedEvents(page);
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 📚 Examples
|
|
150
|
+
|
|
151
|
+
### Testing Event Properties
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
test('tracks user preferences', async ({ page }) => {
|
|
155
|
+
await page.goto('/settings');
|
|
156
|
+
await page.getByLabel('Theme').selectOption('dark');
|
|
157
|
+
await page.getByText('Save').click();
|
|
158
|
+
|
|
159
|
+
await expect(page).toHaveFiredEvent('settings_updated', {
|
|
160
|
+
theme: 'dark',
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Testing a Funnel
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
test('tracks funnel events', async ({ page }) => {
|
|
169
|
+
await page.goto('/product');
|
|
170
|
+
await expect(page).toHaveFiredEvent('product_viewed');
|
|
171
|
+
|
|
172
|
+
await page.getByText('Add to Cart').click();
|
|
173
|
+
await expect(page).toHaveFiredEvent('add_to_cart');
|
|
174
|
+
|
|
175
|
+
await page.getByText('Checkout').click();
|
|
176
|
+
await expect(page).toHaveFiredEvent('checkout_started');
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 📄 License
|
|
181
|
+
|
|
182
|
+
MIT
|
package/dist/global.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for playwright-posthog custom matchers
|
|
3
|
+
*
|
|
4
|
+
* This file extends Playwright's expect API to include PostHog-specific matchers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Page } from '@playwright/test';
|
|
8
|
+
import type { MatcherConfig } from '../src/matchers';
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
namespace PlaywrightTest {
|
|
12
|
+
interface Matchers<R, T = unknown> {
|
|
13
|
+
/**
|
|
14
|
+
* Assert that a PostHog event was fired with the given name and optional properties.
|
|
15
|
+
*
|
|
16
|
+
* This matcher polls for up to 2 seconds (configurable) to account for the
|
|
17
|
+
* asynchronous nature of analytics events.
|
|
18
|
+
*
|
|
19
|
+
* @param eventName - The name of the event to check for
|
|
20
|
+
* @param expectedProperties - Optional properties to match (subset matching)
|
|
21
|
+
* @param config - Optional configuration for timeout and polling interval
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* await expect(page).toHaveFiredEvent('user_signed_up');
|
|
26
|
+
* await expect(page).toHaveFiredEvent('purchase_completed', {
|
|
27
|
+
* plan: 'pro',
|
|
28
|
+
* amount: 99.99
|
|
29
|
+
* });
|
|
30
|
+
* await expect(page).toHaveFiredEvent('custom_event', {}, {
|
|
31
|
+
* timeout: 5000,
|
|
32
|
+
* pollInterval: 200
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
toHaveFiredEvent(
|
|
37
|
+
eventName: string,
|
|
38
|
+
expectedProperties?: Record<string, any>,
|
|
39
|
+
config?: MatcherConfig
|
|
40
|
+
): Promise<R>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Assert that a PostHog event was NOT fired with the given name and optional properties.
|
|
44
|
+
*
|
|
45
|
+
* @param eventName - The name of the event to check for
|
|
46
|
+
* @param expectedProperties - Optional properties to match (subset matching)
|
|
47
|
+
* @param config - Optional configuration for timeout and polling interval
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* await expect(page).notToHaveFiredEvent('error_occurred');
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
notToHaveFiredEvent(
|
|
55
|
+
eventName: string,
|
|
56
|
+
expectedProperties?: Record<string, any>,
|
|
57
|
+
config?: MatcherConfig
|
|
58
|
+
): Promise<R>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Assert that the page has captured a specific number of events (or any events if count is not provided).
|
|
62
|
+
*
|
|
63
|
+
* @param count - Optional number of events to check for
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* await expect(page).toHaveCapturedEvents(); // At least one event
|
|
68
|
+
* await expect(page).toHaveCapturedEvents(5); // Exactly 5 events
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
toHaveCapturedEvents(count?: number): R;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// This export is necessary to make this a module
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Request } from '@playwright/test';
|
|
2
|
+
import { type CapturedEvent } from './symbols';
|
|
3
|
+
export declare function isPostHogRequest(url: string): boolean;
|
|
4
|
+
export declare function extractEventsFromRequest(request: Request): Promise<CapturedEvent[]>;
|
|
5
|
+
export declare function matchesProperties(actual: Record<string, any> | undefined, expected: Record<string, any> | undefined): boolean;
|
|
6
|
+
//# sourceMappingURL=hog-watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hog-watcher.d.ts","sourceRoot":"","sources":["../src/hog-watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAM/D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOrD;AAUD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,aAAa,EAAE,CAAC,CAiD1B;AA0BD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EACvC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GACxC,OAAO,CAkBT"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isPostHogRequest = isPostHogRequest;
|
|
4
|
+
exports.extractEventsFromRequest = extractEventsFromRequest;
|
|
5
|
+
exports.matchesProperties = matchesProperties;
|
|
6
|
+
const symbols_1 = require("./symbols");
|
|
7
|
+
function isPostHogRequest(url) {
|
|
8
|
+
return (url.includes('/e/') ||
|
|
9
|
+
url.includes('/capture') ||
|
|
10
|
+
url.includes('/batch') ||
|
|
11
|
+
url.includes('/s/'));
|
|
12
|
+
}
|
|
13
|
+
async function extractEventsFromRequest(request) {
|
|
14
|
+
const events = [];
|
|
15
|
+
const debug = (0, symbols_1.isDebugEnabled)();
|
|
16
|
+
try {
|
|
17
|
+
const body = request.postDataJSON();
|
|
18
|
+
if (!body) {
|
|
19
|
+
if (debug) {
|
|
20
|
+
console.log('[playwright-posthog] No POST data found in request');
|
|
21
|
+
}
|
|
22
|
+
return events;
|
|
23
|
+
}
|
|
24
|
+
if (debug) {
|
|
25
|
+
console.log('[playwright-posthog] Request body:', JSON.stringify(body, null, 2));
|
|
26
|
+
}
|
|
27
|
+
if (body.batch && Array.isArray(body.batch)) {
|
|
28
|
+
for (const event of body.batch) {
|
|
29
|
+
events.push(normalizeEvent(event));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (Array.isArray(body)) {
|
|
33
|
+
for (const event of body) {
|
|
34
|
+
events.push(normalizeEvent(event));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (body.event || body.e) {
|
|
38
|
+
events.push(normalizeEvent(body));
|
|
39
|
+
}
|
|
40
|
+
else if (body.data) {
|
|
41
|
+
if (Array.isArray(body.data)) {
|
|
42
|
+
for (const event of body.data) {
|
|
43
|
+
events.push(normalizeEvent(event));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
events.push(normalizeEvent(body.data));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (debug && events.length > 0) {
|
|
51
|
+
console.log(`[playwright-posthog] Extracted ${events.length} event(s):`, events.map((e) => e.event).join(', '));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (debug) {
|
|
56
|
+
console.error('[playwright-posthog] Error parsing PostHog request:', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return events;
|
|
60
|
+
}
|
|
61
|
+
function normalizeEvent(rawEvent) {
|
|
62
|
+
const event = {
|
|
63
|
+
event: rawEvent.event || rawEvent.e || 'unknown',
|
|
64
|
+
properties: rawEvent.properties || rawEvent.p || {},
|
|
65
|
+
timestamp: rawEvent.timestamp || rawEvent.ts || Date.now(),
|
|
66
|
+
distinct_id: rawEvent.distinct_id || rawEvent.d,
|
|
67
|
+
};
|
|
68
|
+
for (const [key, value] of Object.entries(rawEvent)) {
|
|
69
|
+
if (!['event', 'e', 'properties', 'p', 'timestamp', 'ts', 'distinct_id', 'd'].includes(key)) {
|
|
70
|
+
event[key] = value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return event;
|
|
74
|
+
}
|
|
75
|
+
function matchesProperties(actual, expected) {
|
|
76
|
+
if (!expected || Object.keys(expected).length === 0) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (!actual) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
for (const [key, expectedValue] of Object.entries(expected)) {
|
|
83
|
+
const actualValue = actual[key];
|
|
84
|
+
if (!deepEqual(actualValue, expectedValue)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
function deepEqual(a, b) {
|
|
91
|
+
if (a === b)
|
|
92
|
+
return true;
|
|
93
|
+
if (a == null || b == null)
|
|
94
|
+
return a === b;
|
|
95
|
+
if (typeof a !== typeof b)
|
|
96
|
+
return false;
|
|
97
|
+
if (typeof a !== 'object')
|
|
98
|
+
return a === b;
|
|
99
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
100
|
+
if (a.length !== b.length)
|
|
101
|
+
return false;
|
|
102
|
+
return a.every((val, index) => deepEqual(val, b[index]));
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(a) || Array.isArray(b))
|
|
105
|
+
return false;
|
|
106
|
+
const keysA = Object.keys(a);
|
|
107
|
+
const keysB = Object.keys(b);
|
|
108
|
+
if (keysA.length !== keysB.length)
|
|
109
|
+
return false;
|
|
110
|
+
return keysA.every(key => deepEqual(a[key], b[key]));
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=hog-watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hog-watcher.js","sourceRoot":"","sources":["../src/hog-watcher.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { kHogEvents, type CapturedEvent } from './symbols';
|
|
2
|
+
import type { Page, TestType } from '@playwright/test';
|
|
3
|
+
export { matchers } from './matchers';
|
|
4
|
+
export type HogPage = Page & {
|
|
5
|
+
[kHogEvents]: CapturedEvent[];
|
|
6
|
+
};
|
|
7
|
+
export declare function withPostHogTracking<T extends TestType<any, any>>(test: T): T;
|
|
8
|
+
export declare function getCapturedEvents(page: Page): CapturedEvent[];
|
|
9
|
+
export declare function clearCapturedEvents(page: Page): void;
|
|
10
|
+
export type { CapturedEvent } from './symbols';
|
|
11
|
+
export type { MatcherConfig } from './matchers';
|
|
12
|
+
export { isPostHogRequest, extractEventsFromRequest, matchesProperties } from './hog-watcher';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiCA,OAAO,EAAE,UAAU,EAAkB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3E,OAAO,KAAK,EAAE,IAAI,EAAkB,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAKtC,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,CAAC,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;CAC/B,CAAC;AAiBF,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CA2C5E;AAKD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,aAAa,EAAE,CAG7D;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAKpD;AAKD,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchesProperties = exports.extractEventsFromRequest = exports.isPostHogRequest = exports.matchers = void 0;
|
|
4
|
+
exports.withPostHogTracking = withPostHogTracking;
|
|
5
|
+
exports.getCapturedEvents = getCapturedEvents;
|
|
6
|
+
exports.clearCapturedEvents = clearCapturedEvents;
|
|
7
|
+
const symbols_1 = require("./symbols");
|
|
8
|
+
const hog_watcher_1 = require("./hog-watcher");
|
|
9
|
+
var matchers_1 = require("./matchers");
|
|
10
|
+
Object.defineProperty(exports, "matchers", { enumerable: true, get: function () { return matchers_1.matchers; } });
|
|
11
|
+
function withPostHogTracking(test) {
|
|
12
|
+
return test.extend({
|
|
13
|
+
page: async ({ page: originalPage }, use) => {
|
|
14
|
+
const hogPage = originalPage;
|
|
15
|
+
const debug = (0, symbols_1.isDebugEnabled)();
|
|
16
|
+
hogPage[symbols_1.kHogEvents] = [];
|
|
17
|
+
if (debug) {
|
|
18
|
+
console.log('[playwright-posthog] Initializing PostHog event capture');
|
|
19
|
+
}
|
|
20
|
+
await hogPage.route('**/*', async (route, request) => {
|
|
21
|
+
const url = request.url();
|
|
22
|
+
if ((0, hog_watcher_1.isPostHogRequest)(url)) {
|
|
23
|
+
if (debug) {
|
|
24
|
+
console.log(`[playwright-posthog] Intercepted PostHog request: ${url}`);
|
|
25
|
+
}
|
|
26
|
+
const events = await (0, hog_watcher_1.extractEventsFromRequest)(request);
|
|
27
|
+
hogPage[symbols_1.kHogEvents].push(...events);
|
|
28
|
+
if (debug && events.length > 0) {
|
|
29
|
+
console.log(`[playwright-posthog] Total events captured: ${hogPage[symbols_1.kHogEvents].length}`);
|
|
30
|
+
console.log(`[playwright-posthog] Latest events:`, events.map((e) => ({
|
|
31
|
+
event: e.event,
|
|
32
|
+
properties: e.properties,
|
|
33
|
+
})));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
await route.continue();
|
|
37
|
+
});
|
|
38
|
+
await use(hogPage);
|
|
39
|
+
if (debug) {
|
|
40
|
+
console.log(`[playwright-posthog] Test complete. Total events captured: ${hogPage[symbols_1.kHogEvents].length}`);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function getCapturedEvents(page) {
|
|
46
|
+
const hogPage = page;
|
|
47
|
+
return hogPage[symbols_1.kHogEvents] || [];
|
|
48
|
+
}
|
|
49
|
+
function clearCapturedEvents(page) {
|
|
50
|
+
const hogPage = page;
|
|
51
|
+
if (hogPage[symbols_1.kHogEvents]) {
|
|
52
|
+
hogPage[symbols_1.kHogEvents] = [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
var hog_watcher_2 = require("./hog-watcher");
|
|
56
|
+
Object.defineProperty(exports, "isPostHogRequest", { enumerable: true, get: function () { return hog_watcher_2.isPostHogRequest; } });
|
|
57
|
+
Object.defineProperty(exports, "extractEventsFromRequest", { enumerable: true, get: function () { return hog_watcher_2.extractEventsFromRequest; } });
|
|
58
|
+
Object.defineProperty(exports, "matchesProperties", { enumerable: true, get: function () { return hog_watcher_2.matchesProperties; } });
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AA6DA,kDA2CC;AAKD,8CAGC;AAKD,kDAKC;AAzFD,uCAA2E;AAC3E,+CAA2E;AAG3E,uCAAsC;AAA7B,oGAAA,QAAQ,OAAA;AAwBjB,SAAgB,mBAAmB,CAA+B,IAAO;IACvE,OAAO,IAAI,CAAC,MAAM,CAAC;QACjB,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,YAAY,EAAkB,EAAE,GAAqC,EAAE,EAAE;YAC5F,MAAM,OAAO,GAAG,YAAuB,CAAC;YACxC,MAAM,KAAK,GAAG,IAAA,wBAAc,GAAE,CAAC;YAE/B,OAAO,CAAC,oBAAU,CAAC,GAAG,EAAE,CAAC;YAEzB,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAY,EAAE,OAAgB,EAAE,EAAE;gBACnE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;gBAE1B,IAAI,IAAA,8BAAgB,EAAC,GAAG,CAAC,EAAE,CAAC;oBAC1B,IAAI,KAAK,EAAE,CAAC;wBACV,OAAO,CAAC,GAAG,CAAC,qDAAqD,GAAG,EAAE,CAAC,CAAC;oBAC1E,CAAC;oBAED,MAAM,MAAM,GAAG,MAAM,IAAA,sCAAwB,EAAC,OAAO,CAAC,CAAC;oBAEvD,OAAO,CAAC,oBAAU,CAAC,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;oBAEpC,IAAI,KAAK,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC/B,OAAO,CAAC,GAAG,CAAC,+CAA+C,OAAO,CAAC,oBAAU,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;wBACzF,OAAO,CAAC,GAAG,CAAC,qCAAqC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC;4BACnF,KAAK,EAAE,CAAC,CAAC,KAAK;4BACd,UAAU,EAAE,CAAC,CAAC,UAAU;yBACzB,CAAC,CAAC,CAAC,CAAC;oBACP,CAAC;gBACH,CAAC;gBAED,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;YACzB,CAAC,CAAC,CAAC;YAEH,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;YAEnB,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,GAAG,CAAC,8DAA8D,OAAO,CAAC,oBAAU,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAC1G,CAAC;QACH,CAAC;KACF,CAAM,CAAC;AACV,CAAC;AAKD,SAAgB,iBAAiB,CAAC,IAAU;IAC1C,MAAM,OAAO,GAAG,IAAe,CAAC;IAChC,OAAO,OAAO,CAAC,oBAAU,CAAC,IAAI,EAAE,CAAC;AACnC,CAAC;AAKD,SAAgB,mBAAmB,CAAC,IAAU;IAC5C,MAAM,OAAO,GAAG,IAAe,CAAC;IAChC,IAAI,OAAO,CAAC,oBAAU,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,oBAAU,CAAC,GAAG,EAAE,CAAC;IAC3B,CAAC;AACH,CAAC;AAOD,6CAA8F;AAArF,+GAAA,gBAAgB,OAAA;AAAE,uHAAA,wBAAwB,OAAA;AAAE,gHAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
export interface MatcherConfig {
|
|
3
|
+
timeout?: number;
|
|
4
|
+
pollInterval?: number;
|
|
5
|
+
}
|
|
6
|
+
declare function toHaveFiredEvent(page: Page, eventName: string, expectedProperties?: Record<string, any>, config?: MatcherConfig): Promise<{
|
|
7
|
+
pass: boolean;
|
|
8
|
+
message: () => string;
|
|
9
|
+
}>;
|
|
10
|
+
declare function notToHaveFiredEvent(page: Page, eventName: string, expectedProperties?: Record<string, any>, config?: MatcherConfig): Promise<{
|
|
11
|
+
pass: boolean;
|
|
12
|
+
message: () => string;
|
|
13
|
+
}>;
|
|
14
|
+
declare function toHaveCapturedEvents(page: Page, count?: number): {
|
|
15
|
+
pass: boolean;
|
|
16
|
+
message: () => string;
|
|
17
|
+
};
|
|
18
|
+
export declare const matchers: {
|
|
19
|
+
toHaveFiredEvent: typeof toHaveFiredEvent;
|
|
20
|
+
notToHaveFiredEvent: typeof notToHaveFiredEvent;
|
|
21
|
+
toHaveCapturedEvents: typeof toHaveCapturedEvents;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=matchers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../src/matchers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAQ7C,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2DD,iBAAe,gBAAgB,CAC7B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACxC,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,MAAM,CAAA;CAAE,CAAC,CAwCnD;AAED,iBAAe,mBAAmB,CAChC,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACxC,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,MAAM,CAAA;CAAE,CAAC,CAGnD;AAED,iBAAS,oBAAoB,CAC3B,IAAI,EAAE,IAAI,EACV,KAAK,CAAC,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,MAAM,CAAA;CAAE,CAkB1C;AAED,eAAO,MAAM,QAAQ;;;;CAIpB,CAAC"}
|
package/dist/matchers.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchers = void 0;
|
|
4
|
+
const symbols_1 = require("./symbols");
|
|
5
|
+
const hog_watcher_1 = require("./hog-watcher");
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
timeout: 2000,
|
|
8
|
+
pollInterval: 100,
|
|
9
|
+
};
|
|
10
|
+
function formatProps(props) {
|
|
11
|
+
return props ? ` with properties ${JSON.stringify(props)}` : '';
|
|
12
|
+
}
|
|
13
|
+
function formatEventList(events) {
|
|
14
|
+
return events.map(e => e.event).join(', ');
|
|
15
|
+
}
|
|
16
|
+
function buildFailureMessage(eventName, expectedProperties, timeout, events) {
|
|
17
|
+
const lines = [
|
|
18
|
+
`Expected page to have fired event "${eventName}"${formatProps(expectedProperties)}, but it did not.`,
|
|
19
|
+
'',
|
|
20
|
+
`Waited ${timeout}ms and captured ${events.length} total event(s).`,
|
|
21
|
+
'',
|
|
22
|
+
];
|
|
23
|
+
if (events.length === 0) {
|
|
24
|
+
lines.push('No PostHog events were captured at all. Possible reasons:', ' • PostHog is not initialized on the page', ' • The event is sent to a different endpoint', ' • Network interception is not working');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const matchingEvents = events.filter(e => e.event === eventName);
|
|
28
|
+
if (matchingEvents.length > 0) {
|
|
29
|
+
lines.push(`Found ${matchingEvents.length} event(s) named "${eventName}" but properties didn't match:`, '');
|
|
30
|
+
matchingEvents.forEach((event, i) => {
|
|
31
|
+
lines.push(` Event ${i + 1}: ${JSON.stringify(event.properties, null, 2)}`);
|
|
32
|
+
});
|
|
33
|
+
lines.push('', `Expected: ${JSON.stringify(expectedProperties, null, 2)}`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
lines.push(`Events captured: [${formatEventList(events)}]`, '', 'The event name did not match any captured events.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
async function toHaveFiredEvent(page, eventName, expectedProperties, config) {
|
|
42
|
+
const hogPage = page;
|
|
43
|
+
const { timeout, pollInterval } = { ...DEFAULT_CONFIG, ...config };
|
|
44
|
+
const debug = (0, symbols_1.isDebugEnabled)();
|
|
45
|
+
if (debug) {
|
|
46
|
+
console.log(`[playwright-posthog] Looking for: "${eventName}"${formatProps(expectedProperties)}`);
|
|
47
|
+
}
|
|
48
|
+
const startTime = Date.now();
|
|
49
|
+
let foundEvent;
|
|
50
|
+
while (Date.now() - startTime < timeout) {
|
|
51
|
+
const events = hogPage[symbols_1.kHogEvents] || [];
|
|
52
|
+
foundEvent = events.find((event) => {
|
|
53
|
+
return event.event === eventName && (0, hog_watcher_1.matchesProperties)(event.properties, expectedProperties);
|
|
54
|
+
});
|
|
55
|
+
if (foundEvent) {
|
|
56
|
+
if (debug) {
|
|
57
|
+
console.log(`[playwright-posthog] ✓ Found after ${Date.now() - startTime}ms`);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
62
|
+
}
|
|
63
|
+
const pass = !!foundEvent;
|
|
64
|
+
return {
|
|
65
|
+
pass,
|
|
66
|
+
message: () => {
|
|
67
|
+
if (pass) {
|
|
68
|
+
return `Expected page NOT to have fired event "${eventName}"${formatProps(expectedProperties)}, but it did.`;
|
|
69
|
+
}
|
|
70
|
+
return buildFailureMessage(eventName, expectedProperties, timeout, hogPage[symbols_1.kHogEvents] || []);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function notToHaveFiredEvent(page, eventName, expectedProperties, config) {
|
|
75
|
+
const result = await toHaveFiredEvent(page, eventName, expectedProperties, config);
|
|
76
|
+
return { pass: !result.pass, message: result.message };
|
|
77
|
+
}
|
|
78
|
+
function toHaveCapturedEvents(page, count) {
|
|
79
|
+
const events = page[symbols_1.kHogEvents] || [];
|
|
80
|
+
const actual = events.length;
|
|
81
|
+
const pass = count === undefined ? actual > 0 : actual === count;
|
|
82
|
+
return {
|
|
83
|
+
pass,
|
|
84
|
+
message: () => {
|
|
85
|
+
if (count === undefined) {
|
|
86
|
+
return pass
|
|
87
|
+
? `Expected no events, but captured ${actual}.`
|
|
88
|
+
: `Expected events, but none were captured.`;
|
|
89
|
+
}
|
|
90
|
+
return pass
|
|
91
|
+
? `Expected NOT to capture ${count} event(s), but did.`
|
|
92
|
+
: `Expected ${count} event(s), got ${actual}. Events: [${formatEventList(events)}]`;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
exports.matchers = {
|
|
97
|
+
toHaveFiredEvent,
|
|
98
|
+
notToHaveFiredEvent,
|
|
99
|
+
toHaveCapturedEvents,
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=matchers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchers.js","sourceRoot":"","sources":["../src/matchers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const kHogEvents: unique symbol;
|
|
2
|
+
export declare function isDebugEnabled(): boolean;
|
|
3
|
+
export interface CapturedEvent {
|
|
4
|
+
event: string;
|
|
5
|
+
properties?: Record<string, any>;
|
|
6
|
+
timestamp?: string | number;
|
|
7
|
+
distinct_id?: string;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=symbols.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../src/symbols.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,UAAU,eAAuB,CAAC;AAM/C,wBAAgB,cAAc,IAAI,OAAO,CAGxC;AAKD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB"}
|
package/dist/symbols.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.kHogEvents = void 0;
|
|
4
|
+
exports.isDebugEnabled = isDebugEnabled;
|
|
5
|
+
exports.kHogEvents = Symbol('hog:events');
|
|
6
|
+
function isDebugEnabled() {
|
|
7
|
+
const debug = process.env.DEBUG;
|
|
8
|
+
return debug === 'true' || debug === '1' || debug === 'playwright-posthog';
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=symbols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbols.js","sourceRoot":"","sources":["../src/symbols.ts"],"names":[],"mappings":";;;AAeA,wCAGC;AATY,QAAA,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAM/C,SAAgB,cAAc;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;IAChC,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,oBAAoB,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example test file demonstrating playwright-posthog usage
|
|
3
|
+
*
|
|
4
|
+
* This file shows various patterns for testing PostHog analytics
|
|
5
|
+
* events in your Playwright tests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test as base, expect as baseExpect } from '@playwright/test';
|
|
9
|
+
import { withPostHogTracking, matchers } from 'playwright-posthog';
|
|
10
|
+
|
|
11
|
+
// Setup: extend your test with PostHog tracking
|
|
12
|
+
const test = withPostHogTracking(base);
|
|
13
|
+
const expect = baseExpect.extend(matchers);
|
|
14
|
+
|
|
15
|
+
test.describe('PostHog Analytics Tracking', () => {
|
|
16
|
+
test('basic event tracking', async ({ page }) => {
|
|
17
|
+
await page.goto('https://example.com');
|
|
18
|
+
|
|
19
|
+
await page.getByText('Sign Up').click();
|
|
20
|
+
|
|
21
|
+
await expect(page).toHaveFiredEvent('signup_clicked');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('event with properties', async ({ page }) => {
|
|
25
|
+
await page.goto('https://example.com/pricing');
|
|
26
|
+
|
|
27
|
+
await page.getByTestId('plan-pro').click();
|
|
28
|
+
|
|
29
|
+
await expect(page).toHaveFiredEvent('plan_selected', {
|
|
30
|
+
plan: 'pro',
|
|
31
|
+
price: 99.99,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('multiple events in sequence', async ({ page }) => {
|
|
36
|
+
await page.goto('https://example.com/product/123');
|
|
37
|
+
|
|
38
|
+
await expect(page).toHaveFiredEvent('product_viewed', {
|
|
39
|
+
product_id: '123',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await page.getByText('Add to Cart').click();
|
|
43
|
+
|
|
44
|
+
await expect(page).toHaveFiredEvent('add_to_cart', {
|
|
45
|
+
product_id: '123',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await page.getByText('Checkout').click();
|
|
49
|
+
await expect(page).toHaveFiredEvent('checkout_started');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('negative assertion - event should NOT fire', async ({ page }) => {
|
|
53
|
+
await page.goto('https://example.com');
|
|
54
|
+
|
|
55
|
+
await page.getByText('About').click();
|
|
56
|
+
|
|
57
|
+
await expect(page).notToHaveFiredEvent('error_occurred');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('custom timeout for slow events', async ({ page }) => {
|
|
61
|
+
await page.goto('https://example.com');
|
|
62
|
+
|
|
63
|
+
await page.getByText('Submit').click();
|
|
64
|
+
|
|
65
|
+
await expect(page).toHaveFiredEvent(
|
|
66
|
+
'form_submitted',
|
|
67
|
+
{ form_type: 'contact' },
|
|
68
|
+
{ timeout: 5000 }
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('partial property matching', async ({ page }) => {
|
|
73
|
+
await page.goto('https://example.com/settings');
|
|
74
|
+
|
|
75
|
+
await page.getByLabel('Theme').selectOption('dark');
|
|
76
|
+
await page.getByText('Save').click();
|
|
77
|
+
|
|
78
|
+
await expect(page).toHaveFiredEvent('settings_updated', {
|
|
79
|
+
theme: 'dark',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('checking event count', async ({ page }) => {
|
|
84
|
+
await page.goto('https://example.com');
|
|
85
|
+
|
|
86
|
+
await expect(page).toHaveCapturedEvents();
|
|
87
|
+
|
|
88
|
+
await expect(page).toHaveCapturedEvents(3);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test.describe('Advanced Usage', () => {
|
|
93
|
+
test('using utility functions', async ({ page }) => {
|
|
94
|
+
const { getCapturedEvents, clearCapturedEvents } = await import('playwright-posthog');
|
|
95
|
+
|
|
96
|
+
await page.goto('https://example.com/step1');
|
|
97
|
+
|
|
98
|
+
let events = getCapturedEvents(page);
|
|
99
|
+
console.log('Step 1 events:', events);
|
|
100
|
+
|
|
101
|
+
clearCapturedEvents(page);
|
|
102
|
+
|
|
103
|
+
await page.goto('https://example.com/step2');
|
|
104
|
+
|
|
105
|
+
events = getCapturedEvents(page);
|
|
106
|
+
console.log('Step 2 events:', events);
|
|
107
|
+
|
|
108
|
+
await expect(page).toHaveFiredEvent('step2_viewed');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('complex property matching', async ({ page }) => {
|
|
112
|
+
await page.goto('https://example.com/checkout');
|
|
113
|
+
|
|
114
|
+
await page.fill('[name=email]', 'user@example.com');
|
|
115
|
+
await page.fill('[name=card]', '4242424242424242');
|
|
116
|
+
await page.getByText('Pay').click();
|
|
117
|
+
|
|
118
|
+
await expect(page).toHaveFiredEvent('payment_submitted', {
|
|
119
|
+
email: 'user@example.com',
|
|
120
|
+
payment_method: 'card',
|
|
121
|
+
metadata: {
|
|
122
|
+
source: 'web',
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Playwright configuration for playwright-posthog
|
|
3
|
+
*
|
|
4
|
+
* See https://playwright.dev/docs/test-configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
testDir: './tests',
|
|
11
|
+
|
|
12
|
+
// Run tests in files in parallel
|
|
13
|
+
fullyParallel: true,
|
|
14
|
+
|
|
15
|
+
// Fail the build on CI if you accidentally left test.only in the source code
|
|
16
|
+
forbidOnly: !!process.env.CI,
|
|
17
|
+
|
|
18
|
+
// Retry on CI only
|
|
19
|
+
retries: process.env.CI ? 2 : 0,
|
|
20
|
+
|
|
21
|
+
// Opt out of parallel tests on CI
|
|
22
|
+
workers: process.env.CI ? 1 : undefined,
|
|
23
|
+
|
|
24
|
+
// Reporter to use
|
|
25
|
+
reporter: 'html',
|
|
26
|
+
|
|
27
|
+
use: {
|
|
28
|
+
// Base URL to use in actions like `await page.goto('/')`
|
|
29
|
+
baseURL: 'http://localhost:3000',
|
|
30
|
+
|
|
31
|
+
// Collect trace when retrying the failed test
|
|
32
|
+
trace: 'on-first-retry',
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// Configure projects for major browsers
|
|
36
|
+
projects: [
|
|
37
|
+
{
|
|
38
|
+
name: 'chromium',
|
|
39
|
+
use: { ...devices['Desktop Chrome'] },
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
name: 'firefox',
|
|
44
|
+
use: { ...devices['Desktop Firefox'] },
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
name: 'webkit',
|
|
49
|
+
use: { ...devices['Desktop Safari'] },
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Test against mobile viewports
|
|
53
|
+
{
|
|
54
|
+
name: 'Mobile Chrome',
|
|
55
|
+
use: { ...devices['Pixel 5'] },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Mobile Safari',
|
|
59
|
+
use: { ...devices['iPhone 12'] },
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
// Run your local dev server before starting the tests
|
|
64
|
+
webServer: {
|
|
65
|
+
command: 'npm run dev',
|
|
66
|
+
url: 'http://localhost:3000',
|
|
67
|
+
reuseExistingServer: !process.env.CI,
|
|
68
|
+
},
|
|
69
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "playwright-posthog",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Test PostHog analytics events in your Playwright tests with type-safe assertions and automatic event capture",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"examples",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc && cp src/global.d.ts dist/global.d.ts",
|
|
23
|
+
"clean": "rm -rf dist",
|
|
24
|
+
"rebuild": "npm run clean && npm run build",
|
|
25
|
+
"prepublishOnly": "npm run rebuild",
|
|
26
|
+
"test": "npm run build && playwright test",
|
|
27
|
+
"test:ui": "npm run build && playwright test --ui",
|
|
28
|
+
"test:debug": "npm run build && playwright test --debug",
|
|
29
|
+
"serve": "npx http-server test-app -p 8080"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"playwright",
|
|
33
|
+
"posthog",
|
|
34
|
+
"analytics",
|
|
35
|
+
"testing",
|
|
36
|
+
"e2e",
|
|
37
|
+
"tracking",
|
|
38
|
+
"test",
|
|
39
|
+
"fixture",
|
|
40
|
+
"matcher",
|
|
41
|
+
"assertions",
|
|
42
|
+
"event-tracking",
|
|
43
|
+
"product-analytics",
|
|
44
|
+
"typescript"
|
|
45
|
+
],
|
|
46
|
+
"author": "playwright-posthog contributors",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=16.0.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@playwright/test": "^1.40.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@playwright/test": "^1.40.0",
|
|
56
|
+
"@types/node": "^25.0.3",
|
|
57
|
+
"http-server": "^14.1.1",
|
|
58
|
+
"typescript": "^5.3.0"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/yourusername/playwright-posthog.git"
|
|
63
|
+
},
|
|
64
|
+
"bugs": {
|
|
65
|
+
"url": "https://github.com/yourusername/playwright-posthog/issues"
|
|
66
|
+
},
|
|
67
|
+
"homepage": "https://github.com/yourusername/playwright-posthog#readme"
|
|
68
|
+
}
|