diff --git a/tracker/.prettierignore b/tracker/.prettierignore
index 39b9daca06..7a3a848c23 100644
--- a/tracker/.prettierignore
+++ b/tracker/.prettierignore
@@ -1,3 +1,4 @@
src/p.js
src/plausible.js
+npm_package/plausible.js
node_modules/
diff --git a/tracker/test/outbound-links.spec.ts b/tracker/test/outbound-links.spec.ts
index 632bad9142..cc733db173 100644
--- a/tracker/test/outbound-links.spec.ts
+++ b/tracker/test/outbound-links.spec.ts
@@ -31,7 +31,7 @@ for (const mode of ['web', 'esm']) {
fulfill: {
status: 200,
contentType: 'text/html',
- body: '
other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
@@ -78,7 +78,7 @@ for (const mode of ['web', 'esm']) {
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
@@ -151,7 +151,7 @@ for (const mode of ['legacy', 'web'])
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
@@ -201,7 +201,7 @@ for (const mode of ['legacy', 'web'])
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
@@ -253,7 +253,7 @@ for (const mode of ['legacy', 'web'])
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
@@ -340,7 +340,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 2,
mockRequestTimeout: 2000
@@ -396,7 +396,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
@@ -448,7 +448,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
@@ -478,7 +478,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: OTHER_PAGE_BODY
},
awaitedRequestCount: 2,
mockRequestTimeout: 2000
@@ -522,3 +522,13 @@ test.describe('outbound links feature when using legacy .compat extension', () =
})
})
})
+
+const OTHER_PAGE_BODY = /* HTML */ `
+
+
+ other page
+
+
+ other page
+
+ `
diff --git a/tracker/test/tagged-events.spec.ts b/tracker/test/tagged-events.spec.ts
index ae60605568..c1e2744f65 100644
--- a/tracker/test/tagged-events.spec.ts
+++ b/tracker/test/tagged-events.spec.ts
@@ -26,7 +26,15 @@ test.beforeEach(async ({ page }) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
- body: 'mocked pagemocked page'
+ body: /* HTML */ `
+
+
+ mocked page
+
+
+ mocked page
+
+ `
})
})
})
@@ -40,7 +48,7 @@ for (const mode of ['web', 'esm']) {
testId,
scriptConfig: switchByMode(
{
- web: { ...DEFAULT_CONFIG },
+ web: config,
esm: ``
@@ -457,8 +465,12 @@ for (const mode of ['legacy', 'web']) {
action: () => page.click('circle'),
expectedRequests: [
{
- n: 'link click'
- // bug with p.url, can't assert
+ n: 'link click',
+ p: {
+ expected: { url: {} },
+ __expectation__: (actual) =>
+ actual && JSON.stringify(actual) === '{"url":{}}'
+ }
}
],
shouldIgnoreRequest: [isPageviewEvent, isEngagementEvent]
@@ -568,7 +580,15 @@ test.describe('tagged events feature when using legacy .compat extension', () =>
fulfill: {
status: 200,
contentType: 'text/html',
- body: 'other pageother page'
+ body: /* HTML */ `
+
+
+ other page
+
+
+ other page
+
+ `
},
awaitedRequestCount: 2,
mockRequestTimeout: 2000
diff --git a/tracker/test/transform_request.spec.ts b/tracker/test/transform-request.spec.ts
similarity index 59%
rename from tracker/test/transform_request.spec.ts
rename to tracker/test/transform-request.spec.ts
index 7823142e8c..8e02b997db 100644
--- a/tracker/test/transform_request.spec.ts
+++ b/tracker/test/transform-request.spec.ts
@@ -6,12 +6,14 @@ import {
e,
expectPlausibleInAction,
hideAndShowCurrentTab,
+ isPageviewEvent,
isEngagementEvent,
switchByMode
} from './support/test-utils'
-import { test } from '@playwright/test'
+import { test, expect } from '@playwright/test'
import { ScriptConfig } from './support/types'
import { LOCAL_SERVER_ADDR } from './support/server'
+
const DEFAULT_CONFIG: ScriptConfig = {
domain: 'example.com',
endpoint: `${LOCAL_SERVER_ADDR}/api/event`,
@@ -263,3 +265,178 @@ for (const mode of ['web', 'esm']) {
})
})
}
+
+test.describe(`transformRequest examples from /docs work`, () => {
+ test.beforeEach(async ({ page }) => {
+ await page
+ .context()
+ .route(new RegExp('(http|https)://example\\.com.*'), async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: 'text/html',
+ body: /* HTML */ `
+
+
+ mocked page
+
+
+ mocked page
+
+ `
+ })
+ })
+ })
+
+ test('you can omit automatically tracked url property from tagged link clicks', async ({
+ page
+ }, { testId }) => {
+ function omitAutomaticUrlProperty(payload) {
+ if (payload.p && payload.p.url) {
+ delete payload.p.url
+ }
+ return payload
+ }
+ const config = {
+ ...DEFAULT_CONFIG,
+ transformRequest: omitAutomaticUrlProperty
+ }
+ const { url } = await initializePageDynamically(page, {
+ testId,
+ scriptConfig: config,
+ bodyContent: /* HTML */ `Purchase`
+ })
+
+ await expectPlausibleInAction(page, {
+ action: async () => {
+ await page.goto(url)
+ await page.click('a')
+ },
+ expectedRequests: [
+ {
+ n: 'Purchase',
+ p: { discounted: 'true' } // <-- no url property
+ }
+ ],
+ shouldIgnoreRequest: [isPageviewEvent, isEngagementEvent]
+ })
+ await expect(page.getByText('mocked page')).toBeVisible()
+ })
+
+ for (const { hashBasedRouting, urlSuffix, expectedUrlSuffix } of [
+ {
+ hashBasedRouting: true,
+ urlSuffix:
+ '?utm_source=example&utm_medium=referral&utm_campaign=test#fragment',
+ expectedUrlSuffix: '#fragment'
+ },
+ {
+ hashBasedRouting: false,
+ urlSuffix: '?utm_source=example&utm_medium=referral&utm_campaign=test',
+ expectedUrlSuffix: ''
+ }
+ ]) {
+ test(`you can omit UTM properties from pageview urls (hashBasedRouting: ${hashBasedRouting})`, async ({
+ page
+ }, { testId }) => {
+ function omitUTMProperties(payload) {
+ const parts = payload.u.split('?')
+ let urlWithoutQuery = parts.shift()
+
+ if (payload.h) {
+ const fragment = parts.join('?').split('#')[1]
+ urlWithoutQuery =
+ typeof fragment === 'string'
+ ? urlWithoutQuery + '#' + fragment
+ : urlWithoutQuery
+ }
+
+ payload.u = urlWithoutQuery
+ return payload
+ }
+
+ const config = {
+ ...DEFAULT_CONFIG,
+ hashBasedRouting,
+ transformRequest: omitUTMProperties
+ }
+
+ // the star path is needed for the dynamic page to load when accessing it with query params
+ const path = '*'
+ const { url } = await initializePageDynamically(page, {
+ testId,
+ path,
+ scriptConfig: config,
+ bodyContent: ''
+ })
+
+ const [actualUrl] = url.split('*')
+
+ await expectPlausibleInAction(page, {
+ action: async () => {
+ await page.goto(`${actualUrl}${urlSuffix}`)
+ // await page.click('a')
+ },
+ expectedRequests: [
+ {
+ n: 'pageview',
+ u: `${LOCAL_SERVER_ADDR}${actualUrl}${expectedUrlSuffix}`
+ }
+ ],
+ shouldIgnoreRequest: [isEngagementEvent]
+ })
+ })
+ }
+
+ test('you can track pages using their canonical url', async ({ page }, {
+ testId
+ }) => {
+ function rewriteUrlToCanonicalUrl(payload) {
+ // Get the canonical URL element
+ const canonicalMeta = document.querySelector('link[rel="canonical"]')
+ // Use the canonical URL if it exists, falling back on the regular URL when it doesn't.
+ if (canonicalMeta) {
+ // @ts-expect-error - canonicalMeta definitely has the href attribute
+ payload.u = canonicalMeta.href + window.location.search
+ }
+ return payload
+ }
+
+ // the star path is needed for the dynamic page to load when accessing it with query params
+ const nonCanonicalPath = '/products/clothes/shoes/banana-leather-shoe*'
+ const { url } = await initializePageDynamically(page, {
+ testId,
+ path: nonCanonicalPath,
+ scriptConfig: /* HTML */ `
+
+
+ `,
+ bodyContent: ''
+ })
+ const [actualUrl] = url.split('*')
+
+ await expectPlausibleInAction(page, {
+ action: async () => {
+ await page.goto(`${actualUrl}?utm_source=example`)
+ },
+ expectedRequests: [
+ {
+ n: 'pageview',
+ u: `${LOCAL_SERVER_ADDR}/products/banana-leather-shoe?utm_source=example`
+ }
+ ],
+ shouldIgnoreRequest: [isEngagementEvent]
+ })
+ })
+})