Skip to content

Commit

Permalink
chore: add masking spec in playwright
Browse files Browse the repository at this point in the history
  • Loading branch information
joshsny committed Feb 28, 2025
1 parent b2dc477 commit cd7335d
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 37 deletions.
87 changes: 50 additions & 37 deletions playground/cypress/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<html>

<head>
<title>PostHog JS Snippet test</title>
</head>

<body>
<button data-cy-button>Some button</button>

Expand All @@ -15,42 +17,42 @@
Send custom event
</button>

<button data-cy-fetch-call-button onclick="makeFetchNetworkPost()">
<button data-cy-fetch-call-button onclick="makeFetchNetworkPost()">
Make fetch post network call
</button>

<button data-cy-xhr-call-button onclick="makeXHRNetworkPOST()">
Make XHR post network call
</button>

<script>
function makeFetchNetworkPost() {
fetch('https://example.com', {
body: 'i am the fetch body',
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
}

function makeXHRNetworkPOST() {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Response:', xhr.responseText);
} else {
console.error('Request failed with status:', xhr.status);
}
};
xhr.onerror = function () {
console.error('Network error');
};
xhr.send('i am the xhr body');
}
</script>
Make XHR post network call
</button>

<script>
function makeFetchNetworkPost() {
fetch('https://example.com', {
body: 'i am the fetch body',
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
}

function makeXHRNetworkPOST() {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Response:', xhr.responseText);
} else {
console.error('Request failed with status:', xhr.status);
}
};
xhr.onerror = function () {
console.error('Network error');
};
xhr.send('i am the xhr body');
}
</script>
<br />

<button data-cy-feature-flag-button onclick="console.log(posthog.isFeatureEnabled('some-feature'))">
Expand All @@ -71,6 +73,10 @@
Sensitive text!
</a>

<a class="secret-text">
Sensitive text for remote masking!
</a>

<!-- TODO: Remove the ="true" once we have fixed the bug with autocapture to ensure E2E that it works -->
<button data-cy-button-sensitive-attributes="true" class='sensitive' id='sensitive' data-sensitive='sensitive'>
Sensitive attributes!
Expand All @@ -84,18 +90,25 @@
Capture an exception
</button>

<button data-cy-exception-string-button onclick="posthog.captureException('I am a plain old string', { extra_prop: 2 })">
<button data-cy-exception-string-button
onclick="posthog.captureException('I am a plain old string', { extra_prop: 2 })">
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 data-cy-dead-click-text style="width: fit-content">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 data-cy-dead-click-text style="width: fit-content">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||[]);
!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>

<p>just some text</p>
</body>
</html>

</html>
58 changes: 58 additions & 0 deletions playwright/session-recording/session-recording-masking.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { test, expect } from '../utils/posthog-playwright-test-base'
import { start } from '../utils/setup'

// Local config not set
// decide comes back - says we shouldn't mask

const remoteMaskingTextSelector = '*'

const startOptions = {
options: {
session_recording: {
compress_events: false,
},
},
decideResponseOverrides: {
sessionRecording: {
endpoint: '/ses/',
masking: {
maskAllInputs: true,
maskTextSelector: remoteMaskingTextSelector,
},
},
capturePerformance: true,
autocapture_opt_out: true,
},
url: './playground/cypress/index.html',
}

test.describe('Session recording - masking', () => {
test.beforeEach(async ({ page, context }) => {
await start(startOptions, page, context)
// await page.resetCapturedEvents()
})

test('masks text', async ({ page }) => {
await page.locator('[data-cy-input]').type('hello posthog!')

await expect(page.locator(remoteMaskingTextSelector).first()).toBeVisible()
// there's nothing to wait for... so, just wait a bit
await page.waitForTimeout(2500)
// no new events
const events = await page.capturedEvents()
const snapshotEvents = events.filter((e) => e.event === '$snapshot')
expect(snapshotEvents.length).toBeGreaterThan(0)

const snapshotsThatIncludeMaskedContent = snapshotEvents.filter((e) => {
const data = e.properties?.['$snapshot_data']

const includesMaskedInput = !!data?.find((d) => JSON.stringify(d).includes('hello posthog!'))

const includesMaskedText = !!data?.find((d) => JSON.stringify(d).includes('just some text'))

return includesMaskedInput || includesMaskedText
})

expect(snapshotsThatIncludeMaskedContent.length).toBe(0)
})
})

0 comments on commit cd7335d

Please sign in to comment.