This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git
The following commit(s) were added to refs/heads/master by this push:
new e7a4422f06eb [fix](website) Count initial Matomo pageviews once and
isolate browser checks (#4567)
e7a4422f06eb is described below
commit e7a4422f06eb02ad97bad186dc7008ad8feb6434
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Sat Oct 31 09:16:41 2026 +0000
[fix](website) Count initial Matomo pageviews once and isolate browser
checks (#4567)
The Matomo plugin records an initial HTML pageview and then records it
again during Docusaurus hydration. It also enables the production
tracker when a compiled site is previewed on localhost. This makes
initial PVs and browser verification traffic enter the production
report.
Keep the upstream tracker initialization and replace its route handler
with a local adapter. Skip the initial route callback, count committed
pathname/query changes once, preserve queries, and ignore document
anchors. Read page titles after Helmet's deferred head update. Load the
tracker only on the configured production origin and exclude browsers
exposing WebDriver.
The browser regression uses real English/Chinese Docusaurus builds with
a tracker double and fulfills every browser request locally. It covers
direct home/document loads, SPA navigation, history, anchors, language
reloads, previews and automated browsers. CI runs it with the publishing
fixtures, including when only the adapter changes.
## Validation
- 46 tests passed, zero skips: publishing tests with Apache enabled, CI
scope tests and Slack attribution tests.
- The English/Chinese production browser fixture passed. Direct loads
record one PV; SPA changes/history each add one; anchors add none; a
language reload records one initial PV. Local/staging/WebDriver cases
load no tracker.
- Browser checks also verify the destination query and updated page
title.
- `git diff --check` passed. The full website was not rebuilt locally.
## Self-review
- Goal: remove duplicate initial PVs and keep known browser checks out
of production analytics; regression checks cover both.
- Scope: analytics adapter, validation and CI selection only.
- Information architecture: common tracking applies to every active
version and locale; per-language content edits are unnecessary.
- URLs/navigation: route targets retain their existing behavior; query
strings are included in SPA analytics and document anchors do not create
PVs.
- Frontend/build/workflows: retain the upstream tracker settings, SSR
guards, build entry points, workflow permissions and browser-tool
installation.
- Validation: tests exercise the generated HTML and actual Docusaurus
lifecycle, plus existing integration coverage.
- Other checks: no additional issues found. WebDriver detection does not
identify automation that conceals itself or sends tracking requests
directly. Mark the deployment date when comparing PV/bounce-rate trends;
historical PVs cannot be corrected with a uniform divisor.
## Versions
- [x] dev
- [x] 4.x
- [x] 3.x
- [x] 2.1 or older (not covered by version/language sync gate)
## Languages
- [x] Chinese
- [x] English
The common adapter also covers Japanese, Korean and Russian.
## Docs Checklist
- [x] Checked by AI
- [x] Test Cases Built
- [x] Updated required version and language counterparts, or explained
why not
- [x] If only one language changed, confirmed whether source/translation
counterparts need sync
---------
Co-authored-by: morningman <[email protected]>
---
.github/workflows/build-check.yml | 4 +-
config/matomo-plugin/README.md | 30 ++++++++
config/matomo-plugin/index.cjs | 24 +++++++
config/matomo-plugin/track.js | 8 +++
config/matomo-plugin/tracking.cjs | 18 +++++
docusaurus.config.js | 2 +-
scripts/i18n/__tests__/ci-scope.test.cjs | 6 +-
scripts/i18n/build-scope.cjs | 4 +-
scripts/publish/__tests__/matomo.test.cjs | 86 ++++++++++++++++++++++
scripts/publish/verify-matomo-fixture.cjs | 115 ++++++++++++++++++++++++++++++
10 files changed, 292 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/build-check.yml
b/.github/workflows/build-check.yml
index f65196ef091f..1b87edac7a8f 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -189,7 +189,9 @@ jobs:
if: steps.detect.outputs.publish_fixture == 'true'
env:
NODE_OPTIONS: --max-old-space-size=4096
- run: node scripts/publish/verify-fixture.cjs --verify-existing
--browser
+ run: |
+ node scripts/publish/verify-fixture.cjs --verify-existing
--browser
+ node scripts/publish/verify-matomo-fixture.cjs
- name: Verify global language fixture in Chromium
if: steps.detect.outputs.content_fixture == 'true'
diff --git a/config/matomo-plugin/README.md b/config/matomo-plugin/README.md
new file mode 100644
index 000000000000..60e44ea61dd7
--- /dev/null
+++ b/config/matomo-plugin/README.md
@@ -0,0 +1,30 @@
+# Website analytics
+
+The local adapter retains `docusaurus-plugin-matomo`'s tracker configuration,
+initial HTML page view, link tracking and heartbeat. Its route hook skips
initial
+hydration, counts subsequent pathname/query changes once, preserves query
+parameters and ignores document-anchor changes. Titles are read after route
+commit, following Docusaurus's deferred Helmet update.
+
+The tracker loads only on the exact production origin in `siteConfig.url`.
+Development builds, local production previews, staging/CDN HTML and browsers
+that expose `navigator.webdriver` do not load it or queue route page views.
+This prevents known browser automation from entering production statistics;
+automation that hides WebDriver or sends tracking requests directly still
+requires investigation and filtering at the analytics/server level.
+
+Run the unit tests and browser fixture with:
+
+```bash
+node --test scripts/publish/__tests__/matomo.test.cjs
+node scripts/publish/verify-matomo-fixture.cjs
+```
+
+The fixture requires Playwright and builds a small English/Chinese site. It
+fulfills all browser requests locally, including a tracker double, so
validating
+the production-origin rules does not send analytics to Apache. It covers direct
+home/document loads, SPA navigation, history, language changes and disabled
+tracking in previews and automation. CI runs it with the publishing fixtures.
+
+Record the deployment date when interpreting PV and bounce-rate changes. The
+previous double-counted initial PVs cannot be corrected with one global
divisor.
diff --git a/config/matomo-plugin/index.cjs b/config/matomo-plugin/index.cjs
new file mode 100644
index 000000000000..149f8a153b7c
--- /dev/null
+++ b/config/matomo-plugin/index.cjs
@@ -0,0 +1,24 @@
+const matomo = require('docusaurus-plugin-matomo');
+
+// Keep the upstream tracker setup, but own Docusaurus lifecycle handling.
+module.exports = function (context) {
+ const plugin = matomo(context);
+ const origin = new URL(context.siteConfig.url).origin;
+ return {
+ ...plugin,
+ getClientModules() {
+ return plugin.getClientModules().length ?
[require.resolve('./track.js')] : [];
+ },
+ injectHtmlTags() {
+ const tags = plugin.injectHtmlTags();
+ return {...tags,headTags:tags.headTags?.map(tag => tag.tagName !==
'script' ? tag : {
+ ...tag,
+ innerHTML:`(function () {
+ if (window.location.origin !== ${JSON.stringify(origin)}
|| window.navigator.webdriver) return;
+ window.__dorisMatomoEnabled = true;
+ ${tag.innerHTML}
+ })();`,
+ })};
+ },
+ };
+};
diff --git a/config/matomo-plugin/track.js b/config/matomo-plugin/track.js
new file mode 100644
index 000000000000..5bdbb0df37b6
--- /dev/null
+++ b/config/matomo-plugin/track.js
@@ -0,0 +1,8 @@
+import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
+import {trackRouteUpdate} from './tracking.cjs';
+
+export default {
+ onRouteDidUpdate(event) {
+ if (ExecutionEnvironment.canUseDOM) trackRouteUpdate(window,event);
+ },
+};
diff --git a/config/matomo-plugin/tracking.cjs
b/config/matomo-plugin/tracking.cjs
new file mode 100644
index 000000000000..3deee2bf17fd
--- /dev/null
+++ b/config/matomo-plugin/tracking.cjs
@@ -0,0 +1,18 @@
+function trackRouteUpdate(browser, {location,previousLocation}) {
+ // The HTML bootstrap already counts a full page load. Only committed SPA
+ // page/query changes count again; document anchors are not new pages.
+ if (!browser.__dorisMatomoEnabled || !previousLocation || !browser._paq
+ || (location.pathname === previousLocation.pathname && location.search
=== previousLocation.search)) return;
+ const url = location.pathname+(location.search || '');
+ // Helmet defers head updates to an animation frame. Read the title after
+ // that frame's callbacks, rather than the previous page's title.
+ browser.requestAnimationFrame(() => {
+ browser.setTimeout(() => {
+ browser._paq.push(['setCustomUrl',url]);
+ browser._paq.push(['setDocumentTitle',browser.document.title]);
+ browser._paq.push(['trackPageView']);
+ });
+ });
+}
+
+module.exports = {trackRouteUpdate};
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 32818aa1f094..0523ca101871 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -381,7 +381,7 @@ const config = {
onInlineAuthors: 'ignore',
}],
'docusaurus-plugin-sass',
- 'docusaurus-plugin-matomo',
+ require.resolve('./config/matomo-plugin/index.cjs'),
// Use custom blog plugin
versionsPlugin,
SKIP_COURSE ? null : [
diff --git a/scripts/i18n/__tests__/ci-scope.test.cjs
b/scripts/i18n/__tests__/ci-scope.test.cjs
index e2a95efa5433..9e3892b62fe2 100644
--- a/scripts/i18n/__tests__/ci-scope.test.cjs
+++ b/scripts/i18n/__tests__/ci-scope.test.cjs
@@ -25,7 +25,9 @@ test('content-only edits retain scoped real builds without
rerunning unchanged f
test('new global language entry points select five languages and every active
version', () => {
for (const file of ['src/components/home-next/NavbarNext.tsx',
'src/components/home-next/LocalizedLink.tsx',
- 'src/components/ContentMetadata.tsx', 'src/utils/localizedRoute.ts',
'src/utils/useLocalizedRoute.ts', 'static/.htaccess', 'config/i18n.js',
'docusaurus.config.js', 'package.json', 'yarn.lock',
'scripts/build-locale.cjs']) {
+ 'src/components/ContentMetadata.tsx', 'src/utils/localizedRoute.ts',
'src/utils/useLocalizedRoute.ts', 'static/.htaccess', 'config/i18n.js',
+ 'config/matomo-plugin/index.cjs', 'config/matomo-plugin/track.js',
'config/matomo-plugin/tracking.cjs',
+ 'docusaurus.config.js', 'package.json', 'yarn.lock',
'scripts/build-locale.cjs']) {
const plan = buildScope([file], versions, managed);
assert.deepEqual(plan.locales, ['en', 'zh-CN', 'ja', 'ko', 'ru'],
file);
assert.equal(plan.docs_versions, '', file);
@@ -43,6 +45,7 @@ test('fixture work follows rendering/publishing dependencies
and plan-only edits
assert.equal(validationScope(['src/utils/localizedRoute.ts']).check_publish_fixture,
false);
assert.equal(validationScope(['scripts/publish/apache.cjs']).check_publish_fixture,
true);
assert.equal(validationScope(['scripts/publish/apache.cjs']).check_content_fixture,
false);
+
assert.equal(validationScope(['config/matomo-plugin/tracking.cjs']).check_publish_fixture,
true);
assert.equal(validationScope(['scripts/i18n/__tests__/localized-route.test.cjs']).check_content_fixture,
false);
assert.equal(validationScope(['scripts/i18n/__tests__/fixtures/content-manifest.cjs']).check_content_fixture,
true);
for (const file of ['package.json', 'yarn.lock',
'.github/workflows/build-check.yml']) {
@@ -67,6 +70,7 @@ test('CI fails early on cheap checks, retains required job
identity and deduplic
for (const fixture of ['content_fixture', 'publish_fixture',
'sampling_fixture'])
assert(browserTools.includes(`steps.detect.outputs.${fixture} ==
'true'`), fixture);
const verify = steps[index('Verify localized static pages')].run;
+ assert.match(steps[index('Verify five-language publishing fixture in
Chromium')].run, /verify-matomo-fixture\.cjs/);
assert.match(verify, /\[ "\$LOCALE" = "ru" \]; then
EXTRA_ARGS\+=\(--source-locales/);
assert.match(verify, /\[ "\$LOCALE" = "ko" \] \|\| \[ "\$LOCALE" = "ru"
\]; then EXTRA_ARGS=\(--themes\)/);
assert.match(verify, /verify-static\.cjs/);
diff --git a/scripts/i18n/build-scope.cjs b/scripts/i18n/build-scope.cjs
index d43149d78b00..85870ef89099 100644
--- a/scripts/i18n/build-scope.cjs
+++ b/scripts/i18n/build-scope.cjs
@@ -15,7 +15,7 @@ const globalLanguageRuntime = file =>
/^src\/utils\/(locale|localization|transla
function validationScope(files) {
const dependencies = files.some(file =>
/^(package.json|yarn.lock)$/.test(file));
const workflow = files.includes('.github/workflows/build-check.yml');
- const publishing = files.some(file =>
/^(scripts\/publish\/|scripts\/i18n\/locale-entry\.cjs$|scripts\/build-locales\.cjs$|scripts\/build-locale\.cjs$|config\/(?:(custom-docusaurus-plugin|ssrTemplate|asset-origin)\.js|locale-entry\.json)$|static\/\.htaccess$|static\/js\/[^/]*bootstrap\.js$|\.github\/workflows\/(cron|manual)-deploy-website\.yml$)/.test(file));
+ const publishing = files.some(file =>
/^(scripts\/publish\/|config\/matomo-plugin\/|scripts\/i18n\/locale-entry\.cjs$|scripts\/build-locales\.cjs$|scripts\/build-locale\.cjs$|config\/(?:(custom-docusaurus-plugin|ssrTemplate|asset-origin)\.js|locale-entry\.json)$|static\/\.htaccess$|static\/js\/[^/]*bootstrap\.js$|\.github\/workflows\/(cron|manual)-deploy-website\.yml$)/.test(file));
const content = files.some(file => globalLanguageRuntime(file)
|| file === 'translations/shared/registry.json'
|| /^scripts\/i18n\/(?!__tests__\/).*\.[cm]?js$/.test(file)
@@ -95,7 +95,7 @@ function buildScope(files, versions, managed = [],
dependencies = null) {
|| /^scripts\/i18n\/verify-(doc-navigation|search)\.cjs$/.test(file));
const contractChanged = files.some(file =>
/^scripts\/i18n\/(content-manifest|manual-state|manual-plugins|native-context|docs-plugin|status-plugin|route-context|page-body|page-body-loader|sitemap)\.cjs$/.test(file)
|| (/^translations\/(pages|blog)\//.test(file) &&
!dependencies?.complete));
- const optimizePublish = contractChanged || searchChanged ||
navigationChanged || files.some(file =>
/^(scripts\/publish\/|scripts\/i18n\/locale-entry\.cjs$|package\.json$|yarn\.lock$|docusaurus\.config\.js$|config\/(?:(i18n|custom-docusaurus-plugin|ssrTemplate|asset-origin)\.js|locale-entry\.json)$|scripts\/build-locales?\.cjs$|static\/\.htaccess$|static\/js\/[^/]*bootstrap\.js$|\.github\/workflows\/(cron-deploy-website|manual-deploy-website|build-check)\.yml$)/.test(file));
+ const optimizePublish = contractChanged || searchChanged ||
navigationChanged || files.some(file =>
/^(scripts\/publish\/|config\/matomo-plugin\/|scripts\/i18n\/locale-entry\.cjs$|package\.json$|yarn\.lock$|docusaurus\.config\.js$|config\/(?:(i18n|custom-docusaurus-plugin|ssrTemplate|asset-origin)\.js|locale-entry\.json)$|scripts\/build-locales?\.cjs$|static\/\.htaccess$|static\/js\/[^/]*bootstrap\.js$|\.github\/workflows\/(cron-deploy-website|manual-deploy-website|build-check)\.yml$
[...]
// Publication changes need the same complete artifact as deployment, on
// the CI runner. Keep local checks scoped to fixtures and Dev versions.
if (optimizePublish) {tier = 'full'; shared();}
diff --git a/scripts/publish/__tests__/matomo.test.cjs
b/scripts/publish/__tests__/matomo.test.cjs
new file mode 100644
index 000000000000..67f9000e074f
--- /dev/null
+++ b/scripts/publish/__tests__/matomo.test.cjs
@@ -0,0 +1,86 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const vm = require('node:vm');
+const plugin = require('../../../config/matomo-plugin/index.cjs');
+const {trackRouteUpdate} =
require('../../../config/matomo-plugin/tracking.cjs');
+
+const context =
{siteConfig:{url:'https://doris.apache.org',themeConfig:{matomo:{
+
matomoUrl:'https://analytics.apache.org/',siteId:'43',phpLoader:'matomo.php',jsLoader:'matomo.js',
+}}}};
+
+function bootstrap(origin = context.siteConfig.url,webdriver = false) {
+ const previous = process.env.NODE_ENV;
+ let instance;
+ try {process.env.NODE_ENV = 'production'; instance = plugin(context);}
+ finally {if (previous === undefined) delete process.env.NODE_ENV; else
process.env.NODE_ENV = previous;}
+ const scripts = [], timers = [];
+ const browser =
{location:{origin},navigator:{webdriver},document:{title:'Initial page',
+ createElement:() => ({}),getElementsByTagName:() =>
[{parentNode:{insertBefore:script => scripts.push(script)}}]},
+ requestAnimationFrame:callback =>
timers.push(callback),setTimeout:callback => timers.push(callback)};
+ const code = instance.injectHtmlTags().headTags.find(tag => tag.tagName
=== 'script').innerHTML;
+ vm.runInNewContext(code,{window:browser,document:browser.document});
+ return {browser,scripts,instance,flush:() => {while (timers.length)
timers.shift()();}};
+}
+
+const location = (pathname,search = '',hash = '') => ({pathname,search,hash});
+const views = browser => (browser._paq || []).filter(command => command[0] ===
'trackPageView').length;
+
+test('a production full load counts once, followed by one PV per SPA
page/query change', () => {
+ const {browser,scripts,instance,flush} = bootstrap();
+ assert.equal(scripts.length,1);
+ assert.equal(scripts[0].src,'https://analytics.apache.org/matomo.js');
+
assert(instance.getClientModules()[0].endsWith('/config/matomo-plugin/track.js'));
+ assert(browser._paq.some(command => command[0] === 'setSiteId' &&
command[1] === '43'));
+ assert(browser._paq.some(command => command[0] === 'enableLinkTracking'));
+ assert(browser._paq.some(command => command[0] ===
'enableHeartBeatTimer'));
+ const first = location('/docs/dev/intro/','?campaign=docs','#intro');
+ trackRouteUpdate(browser,{location:first,previousLocation:null}); flush();
+ assert.equal(views(browser),1);
+ const second = location('/docs/dev/install/','?campaign=docs');
+ trackRouteUpdate(browser,{location:second,previousLocation:first});
+ browser.document.title = 'Install'; flush();
+ assert.equal(views(browser),2);
+ assert(browser._paq.some(command => command[0] === 'setCustomUrl' &&
command[1] === '/docs/dev/install/?campaign=docs'));
+ assert(browser._paq.some(command => command[0] === 'setDocumentTitle' &&
command[1] === 'Install'));
+ const search = location('/docs/dev/install/','?campaign=other');
+ trackRouteUpdate(browser,{location:search,previousLocation:second});
flush();
+ assert.equal(views(browser),3);
+ const translated = location('/zh-CN/docs/dev/install/','?campaign=other');
+ trackRouteUpdate(browser,{location:translated,previousLocation:search});
flush();
+ assert.equal(views(browser),4);
+ trackRouteUpdate(browser,{location:search,previousLocation:translated});
flush();
+ assert.equal(views(browser),5);
+});
+
+test('document anchors and unchanged locations do not count as another page
view', () => {
+ const {browser,flush} = bootstrap();
+ const first = location('/docs/dev/intro/','?from=docs');
+ for (const hash of ['', '#overview', '#examples']) {
+
trackRouteUpdate(browser,{location:{...first,hash},previousLocation:first});
flush();
+ }
+ assert.equal(views(browser),1);
+});
+
+test('local/staging/CDN previews and WebDriver browsers never load or queue
the tracker', () => {
+ for (const origin of
['http://localhost:3000','http://127.0.0.1:3000','http://[::1]:3000',
+
'https://doris.staged.apache.org','https://cdnd.selectdb.com','https://doris.apache.org.example.com'])
{
+ const {browser,scripts,flush} = bootstrap(origin);
+
trackRouteUpdate(browser,{location:location('/next/'),previousLocation:location('/')});
flush();
+ assert.equal(scripts.length,0,origin);
+ assert.equal(views(browser),0,origin);
+ }
+ const {browser,scripts,flush} = bootstrap(context.siteConfig.url,true);
+
trackRouteUpdate(browser,{location:location('/next/'),previousLocation:location('/')});
flush();
+ assert.equal(scripts.length,0);
+ assert.equal(views(browser),0);
+});
+
+test('development and SSR do not register tracking modules or inject a
tracker', t => {
+ const previous = process.env.NODE_ENV;
+ t.after(() => {if (previous === undefined) delete process.env.NODE_ENV;
else process.env.NODE_ENV = previous;});
+ process.env.NODE_ENV = 'development';
+ const instance = plugin(context);
+ assert.deepEqual(instance.getClientModules(),[]);
+ assert.equal(instance.injectHtmlTags().headTags,undefined);
+ assert.doesNotThrow(() => trackRouteUpdate({},
{location:location('/'),previousLocation:null}));
+});
diff --git a/scripts/publish/verify-matomo-fixture.cjs
b/scripts/publish/verify-matomo-fixture.cjs
new file mode 100644
index 000000000000..2ff0cce004ce
--- /dev/null
+++ b/scripts/publish/verify-matomo-fixture.cjs
@@ -0,0 +1,115 @@
+const fs = require('node:fs');
+const path = require('node:path');
+const assert = require('node:assert/strict');
+const {buildLocales} = require('../build-locales.cjs');
+const {createServer} = require('./serve.cjs');
+
+const root = path.resolve(__dirname,'../..');
+const fixture = path.join(root,'.i18n-build/matomo-fixture');
+const production = 'https://doris.apache.org';
+
+async function main() {
+ fs.rmSync(fixture,{recursive:true,force:true});
+ const write = (file,content) => {
+ fs.mkdirSync(path.dirname(path.join(fixture,file)),{recursive:true});
+ fs.writeFileSync(path.join(fixture,file),content);
+ };
+ write('package.json','{"name":"matomo-fixture","private":true}');
+
fs.symlinkSync(path.join(root,'node_modules'),path.join(fixture,'node_modules'));
+ write('docusaurus.config.js',`module.exports = {title:'Matomo
fixture',url:${JSON.stringify(production)},baseUrl:'/',trailingSlash:true,
+
future:{experimental_faster:true},i18n:{defaultLocale:'en',locales:['en','zh-CN']},
+ presets:[['classic',{docs:{sidebarPath:false},blog:false}]],
+
plugins:[${JSON.stringify(path.join(root,'config/matomo-plugin/index.cjs'))}],
+
themeConfig:{matomo:{matomoUrl:'https://analytics.apache.org/',siteId:'43'}}};`);
+ write('src/pages/index.jsx',`import React from 'react';import Link from
'@docusaurus/Link';
+ export default function Home(){return <main><h1>Home</h1><Link
to="/docs/intro/?from=home">Open docs</Link></main>}`);
+ const intro = 'import Link from "@docusaurus/Link";\n\n# Intro\n\n[Next
document](/docs/second/?from=intro)\n\n<Link to="pathname:///zh-CN/docs/intro/"
target="_self">Chinese document</Link>\n';
+ write('docs/intro.md',intro);
+ write('docs/second.md','# Second\n\nSecond page.\n');
+
write('i18n/zh-CN/docusaurus-plugin-content-docs/current/intro.md',intro.replace('#
Intro','# 介绍'));
+ write('i18n/zh-CN/docusaurus-plugin-content-docs/current/second.md','#
第二页\n');
+ await buildLocales({siteDir:fixture,outDir:'build'});
+ const server = createServer(path.join(fixture,'build'));
+ await new Promise(resolve => server.listen(0,'127.0.0.1',resolve));
+ const local = `http://127.0.0.1:${server.address().port}`;
+ const {chromium} = require('playwright'), browser = await
chromium.launch({headless:true});
+ const results = [], errors = [];
+ // A tracker double observes the generated bootstrap and real route hooks.
+ // Every request is fulfilled locally; no production analytics are sent.
+ const tracker = `window.__matomoViews=[];var
url=location.href,title=document.title;
+ function
record(c){if(c[0]==='setCustomUrl')url=c[1];if(c[0]==='setDocumentTitle')title=c[1];
+ if(c[0]==='trackPageView')window.__matomoViews.push({url:new
URL(url,location.origin).href,title});}
+
window._paq.forEach(record);window._paq.push=function(){Array.from(arguments).forEach(record);};`;
+ async function context(origin, automation = false) {
+ const context = await browser.newContext({serviceWorkers:'block'});
+ if (!automation) await context.addInitScript(() =>
Object.defineProperty(navigator,'webdriver',{get:()=>false}));
+ let trackerLoads = 0;
+ await context.route('**/*', async route => {
+ const url = new URL(route.request().url());
+ if (url.href === 'https://analytics.apache.org/matomo.js') {
+ trackerLoads++;
+ return
route.fulfill({contentType:'text/javascript',body:tracker});
+ }
+ if (url.origin !== origin) return route.abort();
+ return route.fulfill({response:await
context.request.get(local+url.pathname+url.search)});
+ });
+ const page = await context.newPage();
+ page.on('pageerror',error => errors.push(error.message));
+ return {context,page,loads:() => trackerLoads};
+ }
+ try {
+ for (const entry of ['/', '/docs/intro/?campaign=direct']) {
+ const {context:session,page,loads} = await context(production);
+ const checkpoints = [];
+ await page.goto(production+entry,{waitUntil:'networkidle'});
+ assert.equal(await page.evaluate(() =>
window.__matomoViews.length),1,entry);
+ assert.equal(loads(),1);
+ checkpoints.push({step:'direct',views:1});
+ if (entry === '/') {
+ await page.getByRole('link',{name:'Open
docs',exact:true}).click();
+ await page.waitForFunction(() => window.__matomoViews.length
=== 2);
+ checkpoints.push({step:'SPA document',views:2});
+ }
+ const before = await page.evaluate(() =>
window.__matomoViews.length);
+ await page.evaluate(() => {location.hash = 'intro';});
+ await page.evaluate(() => new Promise(resolve =>
requestAnimationFrame(() => setTimeout(resolve,0))));
+ assert.equal(await page.evaluate(() =>
window.__matomoViews.length),before);
+ checkpoints.push({step:'anchor',views:before});
+ await page.getByRole('link',{name:'Next
document',exact:true}).click();
+ await page.waitForFunction(count => window.__matomoViews.length
=== count,before+1);
+ assert.equal(await page.evaluate(() =>
window.__matomoViews.at(-1).url),production+'/docs/second/?from=intro');
+ const trackedTitle = await page.evaluate(() =>
window.__matomoViews.at(-1).title);
+ assert(trackedTitle.includes('Second'),trackedTitle);
+ checkpoints.push({step:'SPA next document',views:before+1});
+ await page.goBack();
+ await page.waitForFunction(count => window.__matomoViews.length
=== count,before+2);
+ checkpoints.push({step:'history back',views:before+2});
+ await page.getByRole('link',{name:'Chinese
document',exact:true}).click();
+ await page.waitForURL(production+'/zh-CN/docs/intro/');
+ // Locale switches may reload the document; each new document also
+ // has exactly one bootstrap PV and skips its initial route hook.
+ await page.waitForLoadState('networkidle');
+ assert.equal(await page.evaluate(() =>
window.__matomoViews.filter(view => new URL(view.url).pathname ===
'/zh-CN/docs/intro/').length),1);
+ checkpoints.push({step:'language full load',views:1});
+ results.push({entry,checkpoints,views:await page.evaluate(() =>
window.__matomoViews)});
+ await session.close();
+ }
+ for (const [origin,automation] of
[[local,false],['https://doris.staged.apache.org',false],[production,true]]) {
+ const {context:session,page,loads} = await
context(origin,automation);
+ await page.goto(origin+'/',{waitUntil:'networkidle'});
+ await page.getByRole('link',{name:'Open docs',exact:true}).click();
+ await page.waitForURL(origin+'/docs/intro/?from=home');
+ assert.equal(loads(),0);
+ assert.equal(await page.evaluate(() => typeof
window._paq),'undefined');
+ results.push({origin,automation,trackerLoads:loads()});
+ await session.close();
+ }
+ assert.deepEqual(errors,[]);
+ write('report.json',JSON.stringify({passed:true,results},null,2)+'\n');
+ console.log(JSON.stringify({passed:true,results},null,2));
+ } finally {
+ await browser.close();
+ await new Promise(resolve => server.close(resolve));
+ }
+}
+if (require.main === module) main().catch(error =>
{console.error(error);process.exitCode=1;});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]