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]

Reply via email to