Skip to content

Commit

Permalink
Average Scroll Depth Metric: extracted tracker changes (#4826)
Browse files Browse the repository at this point in the history
* (cherry-pick) implement scroll depth tracking under pageleave variant

* drop unnecessary vars

* remove unused require

* add scroll depth tests

* improve error messages in test util

* reevaluate currentDocumentHeight on page load

* account for dynamically loaded content when initializing documnent height

* remove all semicolons from tracker specs

* allow window and document globals in tracker eslint

* tweak global tracker dir eslint rules

* update comment

* reevaluate document height on scroll

* add test

* remove unneccessary timeout
  • Loading branch information
RobertJoonas authored Nov 21, 2024
1 parent 4d9ea15 commit 2a7d02b
Show file tree
Hide file tree
Showing 20 changed files with 402 additions and 91 deletions.
27 changes: 25 additions & 2 deletions tracker/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,35 @@
"parserOptions": { "ecmaVersion": "latest" },
"env": { "node": true, "es6": true },
"extends": ["eslint:recommended", "plugin:playwright/playwright-test"],
"globals": {
"window": "readonly",
"document": "readonly"
},
"rules": {
"max-len": [0, {"code": 120}],
"max-classes-per-file": [0],
"no-unused-expressions": [1, { "allowShortCircuit": true }],
"no-unused-vars": [2, { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }],
"no-prototype-builtins": [0],
"playwright/no-conditional-in-test": [0]
}
"playwright/no-conditional-in-test": [0],
"playwright/no-wait-for-timeout": "off",
"playwright/expect-expect": [
"error",
{
"assertFunctionNames": [
"expect",
"clickPageElementAndExpectEventRequests",
"expectCustomEvent"
]
}
]
},
"overrides": [
{
"files": ["*.spec.js"],
"rules": {
"semi": ["warn", "never"]
}
}
]
}
46 changes: 46 additions & 0 deletions tracker/src/plausible.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,57 @@
// flag prevents sending multiple pageleaves in those cases.
var pageLeaveSending = false

function getDocumentHeight() {
return Math.max(
document.body.scrollHeight || 0,
document.body.offsetHeight || 0,
document.body.clientHeight || 0,
document.documentElement.scrollHeight || 0,
document.documentElement.offsetHeight || 0,
document.documentElement.clientHeight || 0
)
}

function getCurrentScrollDepthPx() {
var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0
var scrollTop = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0

return currentDocumentHeight <= viewportHeight ? currentDocumentHeight : scrollTop + viewportHeight
}

var currentDocumentHeight = getDocumentHeight()
var maxScrollDepthPx = getCurrentScrollDepthPx()

window.addEventListener('load', function () {
currentDocumentHeight = getDocumentHeight()

// Update the document height again after every 200ms during the
// next 3 seconds. This makes sure dynamically loaded content is
// also accounted for.
var count = 0
var interval = setInterval(function () {
currentDocumentHeight = getDocumentHeight()
if (++count === 15) {clearInterval(interval)}
}, 200)
})

document.addEventListener('scroll', function() {
currentDocumentHeight = getDocumentHeight()
var currentScrollDepthPx = getCurrentScrollDepthPx()

if (currentScrollDepthPx > maxScrollDepthPx) {
maxScrollDepthPx = currentScrollDepthPx
}
})

function triggerPageLeave() {
if (pageLeaveSending) {return}
pageLeaveSending = true
setTimeout(function () {pageLeaveSending = false}, 500)

var payload = {
n: 'pageleave',
sd: Math.round((maxScrollDepthPx / currentDocumentHeight) * 100),
d: dataDomain,
u: currentPageLeaveURL,
}
Expand Down Expand Up @@ -202,6 +246,8 @@
if (isSPANavigation && listeningPageLeave) {
triggerPageLeave();
currentPageLeaveURL = location.href;
currentDocumentHeight = getDocumentHeight()
maxScrollDepthPx = getCurrentScrollDepthPx()
}
{{/if}}

Expand Down
15 changes: 7 additions & 8 deletions tracker/test/custom-event-edge-cases.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const { mockRequest, mockManyRequests, expectCustomEvent } = require('./support/test-utils');
const { expect, test } = require('@playwright/test');
const { LOCAL_SERVER_ADDR } = require('./support/server');

const { mockRequest, mockManyRequests, expectCustomEvent } = require('./support/test-utils')
const { expect, test } = require('@playwright/test')
const { LOCAL_SERVER_ADDR } = require('./support/server')

test.describe('script.file-downloads.outbound-links.tagged-events.js', () => {
test('sends only outbound link event when clicked link is both download and outbound', async ({ page }) => {
Expand All @@ -14,7 +13,7 @@ test.describe('script.file-downloads.outbound-links.tagged-events.js', () => {
const requests = await plausibleRequestMockList
expect(requests.length).toBe(1)
expectCustomEvent(requests[0], 'Outbound Link: Click', {url: downloadURL})
});
})

test('sends file download event when local download link clicked', async ({ page }) => {
await page.goto('/custom-event-edge-case.html')
Expand All @@ -24,7 +23,7 @@ test.describe('script.file-downloads.outbound-links.tagged-events.js', () => {
await page.click('#local-download')

expectCustomEvent(await plausibleRequestMock, 'File Download', {url: downloadURL})
});
})

test('sends only tagged event when clicked link is tagged + outbound + download', async ({ page }) => {
await page.goto('/custom-event-edge-case.html')
Expand All @@ -35,5 +34,5 @@ test.describe('script.file-downloads.outbound-links.tagged-events.js', () => {
const requests = await plausibleRequestMockList
expect(requests.length).toBe(1)
expectCustomEvent(requests[0], 'Foo', {})
});
});
})
})
17 changes: 8 additions & 9 deletions tracker/test/file-downloads.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const { mockRequest, expectCustomEvent, mockManyRequests, metaKey } = require('./support/test-utils');
const { expect, test } = require('@playwright/test');
const { LOCAL_SERVER_ADDR } = require('./support/server');

const { mockRequest, expectCustomEvent, mockManyRequests, metaKey } = require('./support/test-utils')
const { expect, test } = require('@playwright/test')
const { LOCAL_SERVER_ADDR } = require('./support/server')

test.describe('file-downloads extension', () => {
test('sends event and does not start download when link opens in new tab', async ({ page }) => {
Expand All @@ -14,7 +13,7 @@ test.describe('file-downloads extension', () => {

expectCustomEvent(await plausibleRequestMock, 'File Download', { url: downloadURL })
expect(await downloadRequestMock, "should not make download request").toBeNull()
});
})

test('sends event and starts download when link child is clicked', async ({ page }) => {
await page.goto('/file-download.html')
Expand All @@ -26,7 +25,7 @@ test.describe('file-downloads extension', () => {

expectCustomEvent(await plausibleRequestMock, 'File Download', { url: downloadURL })
expect((await downloadRequestMock).url()).toContain(downloadURL)
});
})

test('sends File Download event with query-stripped url property', async ({ page }) => {
await page.goto('/file-download.html')
Expand All @@ -37,7 +36,7 @@ test.describe('file-downloads extension', () => {

const expectedURL = downloadURL.split("?")[0]
expectCustomEvent(await plausibleRequestMock, 'File Download', { url: expectedURL })
});
})

test('starts download only once', async ({ page }) => {
await page.goto('/file-download.html')
Expand All @@ -47,5 +46,5 @@ test.describe('file-downloads extension', () => {
await page.click('#local-download')

expect((await downloadRequestMockList).length).toBe(1)
});
});
})
})
Binary file added tracker/test/fixtures/img/black3x3000.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions tracker/test/fixtures/scroll-depth-content-onscroll.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Plausible Playwright tests</title>
<script defer src="/tracker/js/plausible.local.pageleave.js"></script>
</head>

<body>
<div id="main" style="height: 3000px; background-color: lightblue;">
<a id="navigate-away" href="/manual.html">Navigate away</a>
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
const mainDiv = document.getElementById("main")
let loadedMore = false

window.addEventListener("scroll", () => {
if (!loadedMore && window.innerHeight + window.scrollY >= document.body.offsetHeight) {
loadedMore = true

const newDiv = document.createElement("div")
newDiv.setAttribute("id", "more-content")
newDiv.setAttribute("style", "height: 2000px; background-color: lightcoral;")
document.body.appendChild(newDiv);
}
});
});
</script>
</body>
</html>
25 changes: 25 additions & 0 deletions tracker/test/fixtures/scroll-depth-dynamic-content-load.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer src="/tracker/js/plausible.local.pageleave.js"></script>
</head>
<body>
<br>
<div id="content"></div>
<script>
setTimeout(() => {
document.getElementById('content').innerHTML =
`
<div style="height: 5000px; background-color: gray;">
<a id="navigate-away" href="/manual.html">
Navigate away
</a>
</div>
`
}, 500)
</script>
</body>
</html>
37 changes: 37 additions & 0 deletions tracker/test/fixtures/scroll-depth-hash.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Plausible Playwright tests</title>
<script defer src="/tracker/js/plausible.hash.local.pageleave.js"></script>
</head>

<body>
<nav>
<a id="home-link" href="#home">Home</a>
<a id="about-link" href="#about">About</a>
</nav>

<div id="content"></div>

<script>
const routes = {
'#home': '<h1>Home</h1><p>Welcome to the Home page!</p>',
'#about': '<h1>About</h1><p style="height: 2000px;">Learn more about us here</p>',
};

function loadContent() {
const hash = window.location.hash || '#home';
const content = routes[hash]
document.getElementById('content').innerHTML = content;
}

window.addEventListener('hashchange', loadContent);
window.addEventListener('DOMContentLoaded', loadContent);
</script>
</body>

</html>
16 changes: 16 additions & 0 deletions tracker/test/fixtures/scroll-depth-slow-window-load.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer src="/tracker/js/plausible.local.pageleave.js"></script>
</head>
<body>
<a id="navigate-away" href="/manual.html">
Navigate away
</a>
<br>
<img id="slow-image" src="/img/slow-image" alt="slow image">
</body>
</html>
14 changes: 14 additions & 0 deletions tracker/test/fixtures/scroll-depth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script defer src="/tracker/js/plausible.local.pageleave.js"></script>
<title>Document</title>
</head>
<body>
<div style="height: 5000px; background: repeating-linear-gradient(white, gray 500px);">
<a id="navigate-away" href="/manual.html">Navigate away</a>
</div>
</body>
</html>
8 changes: 4 additions & 4 deletions tracker/test/hash-exclusions.spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const { mockRequest } = require('./support/test-utils')
const { expect, test } = require('@playwright/test');
const { expect, test } = require('@playwright/test')

test.describe('combination of hash and exclusions script extensions', () => {
test('excludes by hash part of the URL', async ({ page }) => {
const plausibleRequestMock = mockRequest(page, '/api/event')

await page.goto('/hash-exclusions.html#this/hash/should/be/ignored');
await page.goto('/hash-exclusions.html#this/hash/should/be/ignored')

expect(await plausibleRequestMock, "should not have sent event").toBeNull()
});
});
})
})
15 changes: 7 additions & 8 deletions tracker/test/manual.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* eslint-disable playwright/expect-expect */
const { clickPageElementAndExpectEventRequests } = require('./support/test-utils')
const { test } = require('@playwright/test');
const { LOCAL_SERVER_ADDR } = require('./support/server');
const { test } = require('@playwright/test')
const { LOCAL_SERVER_ADDR } = require('./support/server')

test.describe('manual extension', () => {
test('can trigger custom events with and without a custom URL if pageview was sent with the default URL', async ({ page }) => {
await page.goto('/manual.html');
await page.goto('/manual.html')

await clickPageElementAndExpectEventRequests(page, '#pageview-trigger', [
{n: 'pageview', u: `${LOCAL_SERVER_ADDR}/manual.html`}
Expand All @@ -16,10 +15,10 @@ test.describe('manual extension', () => {
await clickPageElementAndExpectEventRequests(page, '#custom-event-trigger-custom-url', [
{n: 'CustomEvent', u: `https://example.com/custom/location`}
])
});
})

test('can trigger custom events with and without a custom URL if pageview was sent with a custom URL', async ({ page }) => {
await page.goto('/manual.html');
await page.goto('/manual.html')

await clickPageElementAndExpectEventRequests(page, '#pageview-trigger-custom-url', [
{n: 'pageview', u: `https://example.com/custom/location`}
Expand All @@ -30,5 +29,5 @@ test.describe('manual extension', () => {
await clickPageElementAndExpectEventRequests(page, '#custom-event-trigger-custom-url', [
{n: 'CustomEvent', u: `https://example.com/custom/location`}
])
});
});
})
})
Loading

0 comments on commit 2a7d02b

Please sign in to comment.