Scroll depth: Register `pageleave` listener earlier (#5000)
* Register `pageleave` listener earlier Currently, pageleave listener is only registered when a successful response is received from plausible API. After this change pageleave listener is registered immediately when pageview is triggered, hopefully increasing capture rate. * Codespell
This commit is contained in:
parent
714f7f4603
commit
8d238cd340
|
|
@ -6,3 +6,4 @@ taht
|
||||||
referer
|
referer
|
||||||
referers
|
referers
|
||||||
statics
|
statics
|
||||||
|
firs
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,15 @@
|
||||||
payload.h = 1
|
payload.h = 1
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if pageleave}}
|
||||||
|
if (isPageview) {
|
||||||
|
currentPageLeaveIgnored = false
|
||||||
|
currentPageLeaveURL = payload.u
|
||||||
|
currentPageLeaveProps = payload.p
|
||||||
|
registerPageLeaveListener()
|
||||||
|
}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
request.open('POST', endpoint, true);
|
request.open('POST', endpoint, true);
|
||||||
request.setRequestHeader('Content-Type', 'text/plain');
|
request.setRequestHeader('Content-Type', 'text/plain');
|
||||||
|
|
@ -220,14 +229,6 @@
|
||||||
|
|
||||||
request.onreadystatechange = function() {
|
request.onreadystatechange = function() {
|
||||||
if (request.readyState === 4) {
|
if (request.readyState === 4) {
|
||||||
{{#if pageleave}}
|
|
||||||
if (isPageview) {
|
|
||||||
currentPageLeaveIgnored = false
|
|
||||||
currentPageLeaveURL = payload.u
|
|
||||||
currentPageLeaveProps = payload.p
|
|
||||||
registerPageLeaveListener()
|
|
||||||
}
|
|
||||||
{{/if}}
|
|
||||||
options && options.callback && options.callback({status: request.status})
|
options && options.callback && options.callback({status: request.status})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +247,7 @@
|
||||||
{{#unless hash}}
|
{{#unless hash}}
|
||||||
if (lastPage === location.pathname) return;
|
if (lastPage === location.pathname) return;
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{#if pageleave}}
|
{{#if pageleave}}
|
||||||
if (isSPANavigation && listeningPageLeave) {
|
if (isSPANavigation && listeningPageLeave) {
|
||||||
triggerPageLeave();
|
triggerPageLeave();
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ test.describe('file-downloads extension', () => {
|
||||||
await page.goto('/file-download.html')
|
await page.goto('/file-download.html')
|
||||||
const downloadURL = LOCAL_SERVER_ADDR + '/' + await page.locator('#local-download').getAttribute('href')
|
const downloadURL = LOCAL_SERVER_ADDR + '/' + await page.locator('#local-download').getAttribute('href')
|
||||||
|
|
||||||
const downloadRequestMockList = mockManyRequests(page, downloadURL, 2)
|
const downloadRequestMockList = mockManyRequests({ page, path: downloadURL, numberOfRequests: 2 })
|
||||||
await page.click('#local-download')
|
await page.click('#local-download')
|
||||||
|
|
||||||
expect((await downloadRequestMockList).length).toBe(1)
|
expect((await downloadRequestMockList).length).toBe(1)
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,16 @@
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
if (e.target.id === 'pageview-trigger') {
|
if (e.target.id === 'pageview-trigger') {
|
||||||
window.plausible('pageview')
|
window.plausible('pageview')
|
||||||
}
|
}
|
||||||
if (e.target.id === 'pageview-trigger-custom-url') {
|
if (e.target.id === 'pageview-trigger-custom-url') {
|
||||||
window.plausible('pageview', {u: 'https://example.com/custom/location'})
|
window.plausible('pageview', {u: 'https://example.com/custom/location'})
|
||||||
}
|
}
|
||||||
if (e.target.id === 'custom-event-trigger') {
|
if (e.target.id === 'custom-event-trigger') {
|
||||||
window.plausible('CustomEvent')
|
window.plausible('CustomEvent')
|
||||||
}
|
}
|
||||||
if (e.target.id === 'custom-event-trigger-custom-url') {
|
if (e.target.id === 'custom-event-trigger-custom-url') {
|
||||||
window.plausible('CustomEvent', {u: 'https://example.com/custom/location'})
|
window.plausible('CustomEvent', {u: 'https://example.com/custom/location'})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,14 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a id="navigate-away" href="/manual.html">Navigate away</a>
|
<a id="navigate-away" href="/manual.html">Navigate away</a>
|
||||||
|
<button id="back-button-trigger">Back button</button>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (e.target.id === 'back-button-trigger') {
|
||||||
|
window.history.back()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a id="navigate-away" href="/manual.html">Navigate away</a>
|
<a id="navigate-away" href="/manual.html">Navigate away</a>
|
||||||
|
<a id="to-pageleave-pageview-props" href="/pageleave-pageview-props.html">Navigate to pageleave-pageview props</a>
|
||||||
|
|
||||||
<button id="history-nav">Navigate with history</button>
|
<button id="history-nav">Navigate with history</button>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ test.describe('pageleave extension', () => {
|
||||||
action: () => page.goto('/pageleave-pageview-props.html'),
|
action: () => page.goto('/pageleave-pageview-props.html'),
|
||||||
expectedRequests: [{n: 'pageview', p: {author: 'John'}}]
|
expectedRequests: [{n: 'pageview', p: {author: 'John'}}]
|
||||||
})
|
})
|
||||||
|
|
||||||
await expectPlausibleInAction(page, {
|
await expectPlausibleInAction(page, {
|
||||||
action: () => page.click('#navigate-away'),
|
action: () => page.click('#navigate-away'),
|
||||||
expectedRequests: [{n: 'pageleave', p: {author: 'John'}}]
|
expectedRequests: [{n: 'pageleave', p: {author: 'John'}}]
|
||||||
|
|
@ -148,9 +148,9 @@ test.describe('pageleave extension', () => {
|
||||||
{n: 'pageview', p: {author: 'john'}}
|
{n: 'pageview', p: {author: 'john'}}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
await pageleaveCooldown(page)
|
await pageleaveCooldown(page)
|
||||||
|
|
||||||
await expectPlausibleInAction(page, {
|
await expectPlausibleInAction(page, {
|
||||||
action: () => page.click('#jane-post'),
|
action: () => page.click('#jane-post'),
|
||||||
expectedRequests: [
|
expectedRequests: [
|
||||||
|
|
@ -169,4 +169,25 @@ test.describe('pageleave extension', () => {
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
test('sends a pageleave when plausible API is slow and user navigates away before response is received', async ({ page }) => {
|
||||||
|
await expectPlausibleInAction(page, {
|
||||||
|
action: () => page.goto('/pageleave.html'),
|
||||||
|
expectedRequests: [{n: 'pageview', u: `${LOCAL_SERVER_ADDR}/pageleave.html`}]
|
||||||
|
})
|
||||||
|
|
||||||
|
await expectPlausibleInAction(page, {
|
||||||
|
action: async () => {
|
||||||
|
await page.click('#to-pageleave-pageview-props')
|
||||||
|
await page.click('#back-button-trigger')
|
||||||
|
},
|
||||||
|
expectedRequests: [
|
||||||
|
{n: 'pageleave', u: `${LOCAL_SERVER_ADDR}/pageleave.html`},
|
||||||
|
{n: 'pageview', u: `${LOCAL_SERVER_ADDR}/pageleave-pageview-props.html`, p: {author: 'John'}},
|
||||||
|
{n: 'pageleave', u: `${LOCAL_SERVER_ADDR}/pageleave-pageview-props.html`, p: {author: 'John'}},
|
||||||
|
{n: 'pageview', u: `${LOCAL_SERVER_ADDR}/pageleave.html`}
|
||||||
|
],
|
||||||
|
responseDelay: 1000
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,16 @@ exports.metaKey = function() {
|
||||||
|
|
||||||
// Mocks a specified number of HTTP requests with given path. Returns a promise that resolves to a
|
// Mocks a specified number of HTTP requests with given path. Returns a promise that resolves to a
|
||||||
// list of requests as soon as the specified number of requests is made, or 3 seconds has passed.
|
// list of requests as soon as the specified number of requests is made, or 3 seconds has passed.
|
||||||
const mockManyRequests = function(page, path, numberOfRequests) {
|
const mockManyRequests = function({ page, path, numberOfRequests, responseDelay }) {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
let requestList = []
|
let requestList = []
|
||||||
const requestTimeoutTimer = setTimeout(() => resolve(requestList), 3000)
|
const requestTimeoutTimer = setTimeout(() => resolve(requestList), 3000)
|
||||||
|
|
||||||
page.route(path, (route, request) => {
|
page.route(path, async (route, request) => {
|
||||||
requestList.push(request)
|
requestList.push(request)
|
||||||
|
if (responseDelay) {
|
||||||
|
await delay(responseDelay)
|
||||||
|
}
|
||||||
if (requestList.length === numberOfRequests) {
|
if (requestList.length === numberOfRequests) {
|
||||||
clearTimeout(requestTimeoutTimer)
|
clearTimeout(requestTimeoutTimer)
|
||||||
resolve(requestList)
|
resolve(requestList)
|
||||||
|
|
@ -53,14 +56,14 @@ exports.mockManyRequests = mockManyRequests
|
||||||
/**
|
/**
|
||||||
* A powerful utility function that makes it easy to assert on the event
|
* A powerful utility function that makes it easy to assert on the event
|
||||||
* requests that should or should not have been made after doing a page
|
* requests that should or should not have been made after doing a page
|
||||||
* action (e.g. navigating to the page, clicking a page element, etc).
|
* action (e.g. navigating to the page, clicking a page element, etc).
|
||||||
*
|
*
|
||||||
* @param {Page} page - The Playwright Page object.
|
* @param {Page} page - The Playwright Page object.
|
||||||
* @param {Object} args - The object configuring the action and related expectations.
|
* @param {Object} args - The object configuring the action and related expectations.
|
||||||
* @param {Function} args.action - A function that returns a promise. The function is called
|
* @param {Function} args.action - A function that returns a promise. The function is called
|
||||||
* without arguments, and is `await`ed. This is the action that should or should not trigger
|
* without arguments, and is `await`ed. This is the action that should or should not trigger
|
||||||
* Plausible requests on the page.
|
* Plausible requests on the page.
|
||||||
* @param {Array} [args.expectedRequests] - A list of partial JSON payloads that get matched
|
* @param {Array} [args.expectedRequests] - A list of partial JSON payloads that get matched
|
||||||
* against the bodies of event requests made. An `expectedRequest` is considered as having
|
* against the bodies of event requests made. An `expectedRequest` is considered as having
|
||||||
* occurred if all of its key-value pairs are found from the JSON body of an event request
|
* occurred if all of its key-value pairs are found from the JSON body of an event request
|
||||||
* that was made. The default value is `[]`
|
* that was made. The default value is `[]`
|
||||||
|
|
@ -73,18 +76,26 @@ exports.mockManyRequests = mockManyRequests
|
||||||
* is `expectedRequests.length + refutedRequests.length`.
|
* is `expectedRequests.length + refutedRequests.length`.
|
||||||
* @param {number} [args.expectedRequestCount] - When provided, expects the total amount of
|
* @param {number} [args.expectedRequestCount] - When provided, expects the total amount of
|
||||||
* event requests made to match this number.
|
* event requests made to match this number.
|
||||||
|
* @param {number} [args.responseDelay] - When provided, delays the response from the Plausible
|
||||||
|
* API by the given number of milliseconds.
|
||||||
*/
|
*/
|
||||||
exports.expectPlausibleInAction = async function (page, {
|
exports.expectPlausibleInAction = async function (page, {
|
||||||
action,
|
action,
|
||||||
expectedRequests = [],
|
expectedRequests = [],
|
||||||
refutedRequests = [],
|
refutedRequests = [],
|
||||||
awaitedRequestCount,
|
awaitedRequestCount,
|
||||||
expectedRequestCount
|
expectedRequestCount,
|
||||||
|
responseDelay
|
||||||
}) {
|
}) {
|
||||||
const requestsToExpect = expectedRequestCount ? expectedRequestCount : expectedRequests.length
|
const requestsToExpect = expectedRequestCount ? expectedRequestCount : expectedRequests.length
|
||||||
const requestsToAwait = awaitedRequestCount ? awaitedRequestCount : requestsToExpect + refutedRequests.length
|
const requestsToAwait = awaitedRequestCount ? awaitedRequestCount : requestsToExpect + refutedRequests.length
|
||||||
|
|
||||||
const plausibleRequestMockList = mockManyRequests(page, '/api/event', requestsToAwait)
|
const plausibleRequestMockList = mockManyRequests({
|
||||||
|
page,
|
||||||
|
path: '/api/event',
|
||||||
|
numberOfRequests: requestsToAwait,
|
||||||
|
responseDelay: responseDelay
|
||||||
|
})
|
||||||
await action()
|
await action()
|
||||||
const requestBodies = (await plausibleRequestMockList).map(r => r.postDataJSON())
|
const requestBodies = (await plausibleRequestMockList).map(r => r.postDataJSON())
|
||||||
|
|
||||||
|
|
@ -113,7 +124,7 @@ exports.expectPlausibleInAction = async function (page, {
|
||||||
|
|
||||||
const refutedBodySubsetsErrorMessage = `The following requests were made, but were not expected:\n\n${JSON.stringify(refutedButFoundRequestBodies, null, 4)}`
|
const refutedBodySubsetsErrorMessage = `The following requests were made, but were not expected:\n\n${JSON.stringify(refutedButFoundRequestBodies, null, 4)}`
|
||||||
expect(refutedButFoundRequestBodies, refutedBodySubsetsErrorMessage).toHaveLength(0)
|
expect(refutedButFoundRequestBodies, refutedBodySubsetsErrorMessage).toHaveLength(0)
|
||||||
|
|
||||||
expect(requestBodies.length).toBe(requestsToExpect)
|
expect(requestBodies.length).toBe(requestsToExpect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,3 +148,7 @@ function areFlatObjectsEqual(obj1, obj2) {
|
||||||
|
|
||||||
return keys1.every(key => obj2[key] === obj1[key])
|
return keys1.every(key => obj2[key] === obj1[key])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function delay(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue