Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dead click detection #1463

Merged
merged 46 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d9912ab
first draft
pauldambra Oct 12, 2024
d040e68
fangle tests
pauldambra Oct 13, 2024
cb291e1
test fangling
pauldambra Oct 13, 2024
5d44abf
some tests
pauldambra Oct 13, 2024
63a5cf0
some more tests
pauldambra Oct 13, 2024
ace12c6
Discard changes to package.json
pauldambra Oct 13, 2024
e4a1902
Discard changes to playground/nextjs/package.json
pauldambra Oct 13, 2024
24e0fff
Discard changes to playground/nextjs/pnpm-lock.yaml
pauldambra Oct 13, 2024
e0a714e
Discard changes to playground/nextjs/src/posthog.ts
pauldambra Oct 13, 2024
4ac0e67
fiddling
pauldambra Oct 13, 2024
270a1b6
make event listeners unregisterable
pauldambra Oct 13, 2024
afdb05c
fix
pauldambra Oct 13, 2024
bd4727c
fix
pauldambra Oct 13, 2024
0be7361
scroll detection
pauldambra Oct 13, 2024
6270a4e
fix
pauldambra Oct 13, 2024
0741679
Add e2e test
pauldambra Oct 14, 2024
8680efb
fix
pauldambra Oct 14, 2024
4889887
text selection to block dead click
pauldambra Oct 14, 2024
e66d9df
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 14, 2024
00a3e5a
allow some config
pauldambra Oct 14, 2024
112763a
config fangling
pauldambra Oct 14, 2024
d2b5ef0
less complex i think
pauldambra Oct 14, 2024
2ad1d9b
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 15, 2024
f166fd4
autocapturable elements are never deadclicks
pauldambra Oct 15, 2024
729ae5a
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 15, 2024
8aa7862
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 17, 2024
a823050
Discard changes to react/src/context/PostHogProvider.tsx
pauldambra Oct 18, 2024
5a5fb8f
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 21, 2024
17e9e95
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 23, 2024
47f323c
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 24, 2024
97fecd6
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 24, 2024
0c5a1eb
elements not elements chain
pauldambra Oct 24, 2024
3cd667e
prepare for remote config
pauldambra Oct 25, 2024
8c46756
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 29, 2024
43e2c06
ignore clciks on toolbar
pauldambra Oct 29, 2024
fc24ce2
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 30, 2024
4c64cb3
fix boolean check
pauldambra Oct 30, 2024
9cd5c4a
fix prop access
pauldambra Oct 30, 2024
8c94d0b
fix cypress test
pauldambra Oct 30, 2024
5551a35
clear ph storage between survey tests
pauldambra Oct 30, 2024
6984890
wat
pauldambra Oct 30, 2024
32280e4
rename refactor
pauldambra Oct 30, 2024
9c0b0e3
does it work with a delay?
Phanatic Oct 30, 2024
d1aae92
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 30, 2024
f4876da
Merge branch 'main' into feat/dead-click-detection
pauldambra Oct 30, 2024
4c3f098
this?
pauldambra Oct 31, 2024
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
56 changes: 55 additions & 1 deletion cypress/e2e/capture.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { version } from '../../package.json'

import { getBase64EncodedPayload, getGzipEncodedPayload, getPayload } from '../support/compression'
import { start } from '../support/setup'
import { pollPhCaptures } from '../support/assertions'

const urlWithVersion = new RegExp(`&ver=${version}`)

Expand Down Expand Up @@ -286,7 +287,9 @@ describe('Event capture', () => {
it('does not autocapture anything when /decide is disabled', () => {
start({ options: { autocapture: false, advanced_disable_decide: true }, waitForDecide: false })

cy.get('body').click(100, 100).click(98, 102).click(101, 103)
cy.get('body').click(100, 100)
cy.get('body').click(98, 102)
cy.get('body').click(101, 103)
cy.get('[data-cy-custom-event-button]').click()

// No autocapture events, still captures custom events
Expand Down Expand Up @@ -375,4 +378,55 @@ describe('Event capture', () => {
})
})
})

it('capture dead clicks when configured to', () => {
start({
options: {
capture_dead_clicks: true,
},
})

cy.get('[data-cy-not-an-order-button]').click()

pollPhCaptures('$dead_click').then(() => {
cy.phCaptures({ full: true }).then((captures) => {
const deadClicks = captures.filter((capture) => capture.event === '$dead_click')
expect(deadClicks.length).to.eq(1)
const deadClick = deadClicks[0]
expect(deadClick.properties.$dead_click_last_mutation_timestamp).to.be.a('number')
expect(deadClick.properties.$dead_click_event_timestamp).to.be.a('number')
expect(deadClick.properties.$dead_click_absolute_delay_ms).to.be.greaterThan(2500)
expect(deadClick.properties.$dead_click_scroll_timeout).to.eq(false)
expect(deadClick.properties.$dead_click_mutation_timeout).to.eq(false)
expect(deadClick.properties.$dead_click_absolute_timeout).to.eq(true)
})
})
})

it('does not capture dead click for selected text', () => {
start({
options: {
capture_dead_clicks: true,
},
})

cy.get('[data-cy-dead-click-text]').then(($el) => {
const text = $el.text()
const wordToSelect = text.split(' ')[0]
const position = text.indexOf(wordToSelect)

// click the text to make a selection
cy.get('[data-cy-dead-click-text]')
.trigger('mousedown', position, 0)
.trigger('mousemove', position + wordToSelect.length, 0)
.trigger('mouseup')
.trigger('dblclick', position, 0)
})

cy.wait(1000)
cy.phCaptures({ full: true }).then((captures) => {
const deadClicks = captures.filter((capture) => capture.event === '$dead_click')
expect(deadClicks.length).to.eq(0)
})
})
})
16 changes: 13 additions & 3 deletions cypress/e2e/surveys.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { getPayload } from '../support/compression'
import 'cypress-localstorage-commands'

function onPageLoad(options = {}) {
cy.posthog().then((ph) => {
ph.persistence?.properties().clear()
})

cy.posthogInit(options)
cy.wait('@decide')
cy.wait('@surveys')
Expand Down Expand Up @@ -335,8 +339,13 @@ describe('Surveys', () => {
onPageLoad()
cy.wait('@capture-assertion')
cy.get('.PostHogSurvey12345').shadow().find('.survey-form').should('be.visible')
cy.get('.PostHogSurvey12345').shadow().find('#surveyQuestion0Choice3').click()
cy.get('.PostHogSurvey12345').shadow().find('input[type=text]').type('Product engineer')
cy.get('.PostHogSurvey12345').shadow().find('#surveyQuestion0Choice3').and('not.be.disabled').click()
// TODO: you have to click on the input to activate it, really clicking on the parent should select the input
cy.get('.PostHogSurvey12345').shadow().find('#surveyQuestion0Choice3Open').and('not.be.disabled').click()
cy.get('.PostHogSurvey12345')
.shadow()
.find('input[type=text]#surveyQuestion0Choice3Open')
.type('Product engineer')
cy.get('.PostHogSurvey12345').shadow().find('.form-submit').click()
cy.wait('@capture-assertion').then(async ({ request }) => {
const captures = await getPayload(request)
Expand Down Expand Up @@ -590,7 +599,7 @@ describe('Surveys', () => {
cy.phCaptures().should('include', 'survey sent')
})

it('wigetType is custom selector', () => {
it('widgetType is custom selector', () => {
cy.intercept('GET', '**/surveys/*', {
surveys: [
{
Expand All @@ -615,6 +624,7 @@ describe('Surveys', () => {
onPageLoad()
cy.get('.PostHogWidget123').shadow().find('.ph-survey-widget-tab').should('not.exist')
cy.get('.test-surveys').click()
cy.wait(5000)
cy.get('.PostHogWidget123').shadow().find('.survey-form').should('be.visible')
cy.get('.PostHogWidget123').shadow().find('.survey-question').should('have.text', 'Feedback for us?')
cy.get('.PostHogWidget123')
Expand Down
1 change: 1 addition & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ beforeEach(() => {
'exception-autocapture',
'tracing-headers',
'web-vitals',
'dead-clicks-autocapture',
]
lazyLoadedJSFiles.forEach((key: string) => {
cy.readFile(`dist/${key}.js`).then((body) => {
Expand Down
6 changes: 6 additions & 0 deletions playground/cypress-full/index.html

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions playground/cypress/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
Capture as string
</button>

<div style="display: flex; flex-direction: column; gap: 0.5em;">
<h3>my favourite takeaway has this order now not button in their hero image</h3>
<div>it's a great example of why you need dead click tracking</div>
<img data-cy-not-an-order-button width="400" height="200" alt="my favourite takeaway has this order now not button in their hero image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC" />
</div>

<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getSurveys getActiveMatchingSurveys captureException".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
</script>
Expand Down
Loading
Loading