From 00f9bfcb7b18935352e2cf55a200797c38d5053a Mon Sep 17 00:00:00 2001 From: xantus smith <281845000+smith-909@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:39:09 -0700 Subject: [PATCH] test(e2e): check that a link opened through the SDK is stored as a human click The server records a click when the SDK resolves a link the app opened. Check, for a link opened while the app runs and one that launches it, that exactly one click is stored and that it is not classified as a bot; and that the browser tap before an install is stored as human. --- e2e/harness.mjs | 8 ++++++++ e2e/scenarios.mjs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/e2e/harness.mjs b/e2e/harness.mjs index 741c18e..22dea16 100644 --- a/e2e/harness.mjs +++ b/e2e/harness.mjs @@ -144,6 +144,14 @@ class Context { async count(query) { return Number(this.sql(query)); } + /** Clicks the server stored for a link, split by its bot classification. */ + async humanClicks(link) { + return this.count(`SELECT count(*) FROM click_events WHERE link_id = ${this.q(link.id)} AND NOT is_bot`); + } + async botClicks(link) { + return this.count(`SELECT count(*) FROM click_events WHERE link_id = ${this.q(link.id)} AND is_bot`); + } + async installsSinceStart() { return this.count(`SELECT count(*) FROM install_events WHERE created_at BETWEEN ${this.q(this.startedAt)}::timestamp AND now() AT TIME ZONE 'utc' AND sdk_name = ${this.q(this.device.sdkName)}`); } diff --git a/e2e/scenarios.mjs b/e2e/scenarios.mjs index 6ad75f0..f894cb1 100644 --- a/e2e/scenarios.mjs +++ b/e2e/scenarios.mjs @@ -19,6 +19,8 @@ export const scenarios = [ await t.click(link); await t.dbCheck('the browser click is recorded', async () => (await t.count(`SELECT count(*) FROM click_events WHERE link_id = ${t.q(link.id)}`)) >= 1); + await t.dbCheck('the browser click counts as human', async () => (await t.botClicks(link)) === 0, + async () => `${await t.botClicks(link)} stored as bot`); const first = await t.launch(); const init = first.one('initResult'); @@ -100,6 +102,9 @@ export const scenarios = [ t.check('stored parameters arrive', data?.customParameters?.productId === '9'); t.check('parameters on the tapped URL arrive', data?.customParameters?.campaign === 'spring'); t.check('the callback runs on the main thread', opened[0]?.thread === 'main', opened[0]?.thread); + await t.dbCheck('the open is stored as one human click', async () => (await t.humanClicks(link)) === 1 + && (await t.botClicks(link)) === 0, + async () => `${await t.humanClicks(link)} human, ${await t.botClicks(link)} bot`); }, }, @@ -113,6 +118,9 @@ export const scenarios = [ const opened = entries.all('deepLink'); t.check('onDeepLink fires once', opened.length === 1, `fired ${opened.length} times`); t.check('with the link', opened[0]?.deepLinkData?.shortCode === link.shortCode); + await t.dbCheck('the open is stored as one human click', async () => (await t.humanClicks(link)) === 1 + && (await t.botClicks(link)) === 0, + async () => `${await t.humanClicks(link)} human, ${await t.botClicks(link)} bot`); }, },