Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tracker/.prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
src/p.js
src/plausible.js
npm_package/plausible.js
node_modules/
28 changes: 19 additions & 9 deletions tracker/test/outbound-links.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ for (const mode of ['web', 'esm']) {
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
Expand Down Expand Up @@ -78,7 +78,7 @@ for (const mode of ['web', 'esm']) {
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
Expand Down Expand Up @@ -151,7 +151,7 @@ for (const mode of ['legacy', 'web'])
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
Expand Down Expand Up @@ -201,7 +201,7 @@ for (const mode of ['legacy', 'web'])
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
Expand Down Expand Up @@ -253,7 +253,7 @@ for (const mode of ['legacy', 'web'])
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
Expand Down Expand Up @@ -340,7 +340,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 2,
mockRequestTimeout: 2000
Expand Down Expand Up @@ -396,7 +396,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
Expand Down Expand Up @@ -448,7 +448,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 1
})
Expand Down Expand Up @@ -478,7 +478,7 @@ test.describe('outbound links feature when using legacy .compat extension', () =
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: OTHER_PAGE_BODY
},
awaitedRequestCount: 2,
mockRequestTimeout: 2000
Expand Down Expand Up @@ -522,3 +522,13 @@ test.describe('outbound links feature when using legacy .compat extension', () =
})
})
})

const OTHER_PAGE_BODY = /* HTML */ `<!DOCTYPE html>
<html>
<head>
<title>other page</title>
</head>
<body>
other page
</body>
</html>`
30 changes: 25 additions & 5 deletions tracker/test/tagged-events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ test.beforeEach(async ({ page }) => {
await route.fulfill({
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>mocked page</title></head><body>mocked page</body></html>'
body: /* HTML */ `<!DOCTYPE html>
<html>
<head>
<title>mocked page</title>
</head>
<body>
mocked page
</body>
</html>`
})
})
})
Expand All @@ -40,7 +48,7 @@ for (const mode of ['web', 'esm']) {
testId,
scriptConfig: switchByMode(
{
web: { ...DEFAULT_CONFIG },
web: config,
esm: `<script type="module">import { init, track } from '/tracker/js/npm_package/plausible.js'; init(${JSON.stringify(
config
)})</script>`
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -568,7 +580,15 @@ test.describe('tagged events feature when using legacy .compat extension', () =>
fulfill: {
status: 200,
contentType: 'text/html',
body: '<!DOCTYPE html><html><head><title>other page</title></head><body>other page</body></html>'
body: /* HTML */ `<!DOCTYPE html>
<html>
<head>
<title>other page</title>
</head>
<body>
other page
</body>
</html>`
},
awaitedRequestCount: 2,
mockRequestTimeout: 2000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down Expand Up @@ -263,3 +265,178 @@ for (const mode of ['web', 'esm']) {
})
})
}

test.describe(`transformRequest examples from /docs work`, () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should help verify that what we recommend to users actually works as intended

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 */ `<!DOCTYPE html>
<html>
<head>
<title>mocked page</title>
</head>
<body>
mocked page
</body>
</html>`
})
})
})

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 */ `<a
class="plausible-event-name=Purchase plausible-event-discounted=true"
href="https://example.com/target?user=sensitive"
>Purchase</a
>`
})

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 */ `
<link rel="canonical" href="/products/banana-leather-shoe" />
<script type="module">
import { init, track } from '/tracker/js/npm_package/plausible.js'
init(
${serializeWithFunctions({
...DEFAULT_CONFIG,
transformRequest: rewriteUrlToCanonicalUrl
})}
)
</script>
`,
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]
})
})
})
Loading