This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch feat/oink-core-platform
in repository https://gitbox.apache.org/repos/asf/hugegraph-doc.git

commit fdd1da79eaec22685dcf225e69735d596e966526
Author: dark <[email protected]>
AuthorDate: Fri Sep 4 20:11:17 2026 +0800

    test(e2e): add Chromium release gates
    
    - add a locked Node 24 Playwright workspace
    - verify five-version archive and alias contracts in Chromium
    - assert fixed bilingual Lunr queries rank expected references
    - keep visual evidence advisory and outside the deploy gate
---
 .github/workflows/hugo.yml       | 37 +++++++++++++++++++
 .gitignore                       |  4 ++
 tests/e2e/package-lock.json      | 79 ++++++++++++++++++++++++++++++++++++++++
 tests/e2e/package.json           | 15 ++++++++
 tests/e2e/playwright.config.js   | 29 +++++++++++++++
 tests/e2e/search-ranking.spec.js | 60 ++++++++++++++++++++++++++++++
 tests/e2e/versioning.spec.js     | 59 ++++++++++++++++++++++++++++++
 tests/e2e/visual.spec.js         | 25 +++++++++++++
 8 files changed, 308 insertions(+)

diff --git a/.github/workflows/hugo.yml b/.github/workflows/hugo.yml
index 90a25a680..329da8a15 100644
--- a/.github/workflows/hugo.yml
+++ b/.github/workflows/hugo.yml
@@ -318,6 +318,43 @@ jobs:
           retention-days: 7
           if-no-files-found: warn
 
+  visual:
+    needs: [prepare, aggregate]
+    runs-on: ubuntu-latest
+    timeout-minutes: 15
+    continue-on-error: true
+    permissions: { contents: read }
+    steps:
+      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+        with:
+          ref: ${{ github.event.pull_request.head.sha || github.sha }}
+          persist-credentials: false
+      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+        with:
+          node-version: "24"
+          cache: npm
+          cache-dependency-path: tests/e2e/package-lock.json
+      - uses: 
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+        with:
+          name: hugegraph-site-${{ needs.prepare.outputs.artifact_prefix 
}}-${{ github.run_id }}-${{ github.run_attempt }}
+          path: public-site
+      - name: Capture advisory visual states
+        working-directory: tests/e2e
+        env:
+          SITE_ROOT: ${{ github.workspace }}/public-site
+        run: |
+          npm ci
+          npx playwright install --with-deps chromium
+          npm run test:visual
+      - name: Upload advisory visual evidence
+        if: always()
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7
+        with:
+          name: visual-evidence-${{ github.run_id }}-${{ github.run_attempt }}
+          path: tests/e2e/visual-results
+          retention-days: 7
+          if-no-files-found: warn
+
   # Required check name in .asf.yaml. It gates artifacts without write access.
   deploy:
     if: always()
diff --git a/.gitignore b/.gitignore
index 2e76301fc..cc2d3a994 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,10 @@ public-staging/
 resources/
 node_modules/
 package-lock.json
+!/tests/e2e/package-lock.json
+/tests/e2e/playwright-report/
+/tests/e2e/test-results/
+/tests/e2e/visual-results/
 .hugo_build.lock
 nohup.out
 *.log
diff --git a/tests/e2e/package-lock.json b/tests/e2e/package-lock.json
new file mode 100644
index 000000000..d697d2ae7
--- /dev/null
+++ b/tests/e2e/package-lock.json
@@ -0,0 +1,79 @@
+{
+  "name": "hugegraph-doc-e2e",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "hugegraph-doc-e2e",
+      "devDependencies": {
+        "@playwright/test": "1.55.0"
+      },
+      "engines": {
+        "node": ">=24"
+      }
+    },
+    "node_modules/@playwright/test": {
+      "version": "1.55.0",
+      "resolved": 
"https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz";,
+      "integrity": 
"sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "playwright": "1.55.0"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz";,
+      "integrity": 
"sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/playwright": {
+      "version": "1.55.0",
+      "resolved": 
"https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz";,
+      "integrity": 
"sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "playwright-core": "1.55.0"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "fsevents": "2.3.2"
+      }
+    },
+    "node_modules/playwright-core": {
+      "version": "1.55.0",
+      "resolved": 
"https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz";,
+      "integrity": 
"sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "playwright-core": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    }
+  }
+}
diff --git a/tests/e2e/package.json b/tests/e2e/package.json
new file mode 100644
index 000000000..31e0c64ca
--- /dev/null
+++ b/tests/e2e/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "hugegraph-doc-e2e",
+  "private": true,
+  "engines": {
+    "node": ">=24"
+  },
+  "scripts": {
+    "test": "playwright test",
+    "test:ci": "playwright test versioning.spec.js search-ranking.spec.js 
--reporter=line,html",
+    "test:visual": "playwright test visual.spec.js --reporter=line"
+  },
+  "devDependencies": {
+    "@playwright/test": "1.55.0"
+  }
+}
diff --git a/tests/e2e/playwright.config.js b/tests/e2e/playwright.config.js
new file mode 100644
index 000000000..8a1052461
--- /dev/null
+++ b/tests/e2e/playwright.config.js
@@ -0,0 +1,29 @@
+const { defineConfig } = require("@playwright/test");
+
+const siteRoot = process.env.SITE_ROOT;
+if (!siteRoot) {
+  throw new Error("SITE_ROOT must point to an aggregate site artifact");
+}
+
+module.exports = defineConfig({
+  testDir: ".",
+  testMatch: "*.spec.js",
+  outputDir: "test-results",
+  timeout: 30_000,
+  expect: { timeout: 5_000 },
+  retries: process.env.CI ? 1 : 0,
+  workers: process.env.CI ? 2 : undefined,
+  reporter: [["line"], ["html", { outputFolder: "playwright-report", open: 
"never" }]],
+  use: {
+    baseURL: "http://127.0.0.1:4173";,
+    browserName: "chromium",
+    trace: "retain-on-failure",
+    screenshot: "only-on-failure"
+  },
+  webServer: {
+    command: `python3 -m http.server 4173 --bind 127.0.0.1 --directory 
${JSON.stringify(siteRoot)}`,
+    url: "http://127.0.0.1:4173/";,
+    reuseExistingServer: false,
+    timeout: 30_000
+  }
+});
diff --git a/tests/e2e/search-ranking.spec.js b/tests/e2e/search-ranking.spec.js
new file mode 100644
index 000000000..6c9e362ef
--- /dev/null
+++ b/tests/e2e/search-ranking.spec.js
@@ -0,0 +1,60 @@
+const { test, expect } = require("@playwright/test");
+
+const CASES = {
+  en: [
+    ["introduction", "/docs/introduction/", "Apache HugeGraph Introduction"],
+    ["server", "/docs/quickstart/hugegraph/hugegraph-server/", "HugeGraph 
Server Quick Start"],
+    ["hstore", "/docs/quickstart/hugegraph/hugegraph-hstore/", 
"HugeGraph-Store Quick Start"],
+    ["placement driver", "/docs/quickstart/hugegraph/hugegraph-pd/", 
"HugeGraph-PD Quick Start"],
+    ["computer", "/docs/quickstart/computing/hugegraph-computer/", 
"HugeGraph-Computer Quick Start"],
+    ["loader", "/docs/quickstart/toolchain/hugegraph-loader/", 
"HugeGraph-Loader Quick Start"],
+    ["hubble", "/docs/quickstart/toolchain/hugegraph-hubble/", 
"HugeGraph-Hubble Quick Start"],
+    ["clients", "/docs/clients/", "Clients and APIs"],
+    ["rest api", "/docs/clients/restful-api/", "HugeGraph RESTful API"],
+    ["configuration", "/docs/config/", "HugeGraph-Server Configuration"],
+    ["authentication", "/docs/config/config-authentication/", "Built-in User 
Authentication"],
+    ["download", "/docs/download/download/", "Download Apache HugeGraph"]
+  ],
+  cn: [
+    ["介绍", "/cn/docs/introduction/", "Apache HugeGraph 介绍"],
+    ["服务端", "/cn/docs/quickstart/hugegraph/hugegraph-server/", "HugeGraph 
Server 快速开始"],
+    ["HStore", "/cn/docs/quickstart/hugegraph/hugegraph-hstore/", 
"HugeGraph-Store Quick Start"],
+    ["PD", "/cn/docs/quickstart/hugegraph/hugegraph-pd/", "HugeGraph-PD Quick 
Start"],
+    ["图计算", "/cn/docs/quickstart/computing/hugegraph-computer/", 
"HugeGraph-Computer Quick Start"],
+    ["数据导入", "/cn/docs/quickstart/toolchain/hugegraph-loader/", 
"HugeGraph-Loader Quick Start"],
+    ["图形化界面", "/cn/docs/quickstart/toolchain/hugegraph-hubble/", 
"HugeGraph-Hubble Quick Start"],
+    ["客户端", "/cn/docs/clients/", "客户端与 API"],
+    ["REST API", "/cn/docs/clients/restful-api/", "HugeGraph RESTful API"],
+    ["配置", "/cn/docs/config/", "HugeGraph-Server 配置"],
+    ["认证", "/cn/docs/config/config-authentication/", "HugeGraph 内置用户权限"],
+    ["下载", "/cn/docs/download/download/", "下载 Apache HugeGraph"]
+  ]
+};
+
+for (const [locale, cases] of Object.entries(CASES)) {
+  test(`summary Lunr ranks fixed ${locale} entry queries`, async ({ page }) => 
{
+    for (const [query, expectedRef, expectedTitle] of cases) {
+      await page.goto(locale === "cn" ? "/cn/docs/" : "/docs/");
+      await page.locator("[data-td-shell-search-open]").first().click();
+      const input = page.locator(".td-shell-search__input");
+      await input.fill(query);
+      const pageResults = page
+        .locator("#td-shell-search-results .td-shell-search__group")
+        .first()
+        .locator('[role="option"]');
+      await expect(pageResults.first(), `no Lunr results for 
${query}`).toBeVisible();
+      const titles = await pageResults.evaluateAll((rows) =>
+        rows.slice(0, 3).map((row) =>
+          row.querySelector(".td-shell-search__item-title").textContent.trim()
+        )
+      );
+      expect(
+        titles.some((title) => title.includes(expectedTitle)),
+        `${query} must rank ${expectedRef} in the top three`
+      ).toBe(true);
+      const target = pageResults.filter({ hasText: expectedTitle }).first();
+      await target.click();
+      await expect(page).toHaveURL((url) => url.pathname === expectedRef);
+    }
+  });
+}
diff --git a/tests/e2e/versioning.spec.js b/tests/e2e/versioning.spec.js
new file mode 100644
index 000000000..1e44a2cef
--- /dev/null
+++ b/tests/e2e/versioning.spec.js
@@ -0,0 +1,59 @@
+const { test, expect } = require("@playwright/test");
+
+const VERSION_IDS = ["latest", "1.7", "1.5", "1.3", "1.0"];
+const EXPECTED_IDS = (process.env.EXPECTED_VERSIONS || 
VERSION_IDS.join(",")).split(",");
+
+async function actionManifest(page) {
+  return page.locator("#td-action-manifest").evaluate((node) => 
JSON.parse(node.textContent));
+}
+
+test("aggregate records the immutable five-version manifest", async ({ request 
}) => {
+  const response = await request.get("/build-metadata/versions.json");
+  expect(response.ok()).toBeTruthy();
+  const manifest = await response.json();
+  expect(manifest.schemaVersion).toBe(1);
+  expect(manifest.versions.map((entry) => entry.id)).toEqual(EXPECTED_IDS);
+  for (const entry of manifest.versions) {
+    expect(entry.sha).toMatch(/^[0-9a-f]{40}$/);
+  }
+});
+
+for (const locale of ["en", "cn"]) {
+  test(`latest ${locale} exposes the fixed version order`, async ({ page }) => 
{
+    await page.goto(locale === "cn" ? "/cn/docs/" : "/docs/");
+    const manifest = await actionManifest(page);
+    const action = manifest.actions.find((item) => item.id === 
"switch_version");
+    expect(action.options.map((item) => item.id)).toEqual(VERSION_IDS);
+  });
+}
+
+for (const version of VERSION_IDS.slice(1)) {
+  for (const locale of ["en", "cn"]) {
+    test(`${version} ${locale} is archived and directly reachable`, async ({ 
page }) => {
+      test.skip(!EXPECTED_IDS.includes(version), "latest-only staging 
artifact");
+      const prefix = `/versions/${version}${locale === "cn" ? "/cn" : ""}`;
+      await page.goto(`${prefix}/docs/`);
+      await expect(page.locator('meta[name="robots"]')).toHaveAttribute(
+        "content",
+        /noindex\s*,?\s*follow/i
+      );
+      await expect(page.locator(".td-page-notice--primary")).toBeVisible();
+      const manifest = await actionManifest(page);
+      const action = manifest.actions.find((item) => item.id === 
"switch_version");
+      expect(action.options.map((item) => item.id)).toEqual(VERSION_IDS);
+    });
+  }
+}
+
+test("1.0 flat Server URL remains a static alias", async ({ page }) => {
+  test.skip(!EXPECTED_IDS.includes("1.0"), "latest-only staging artifact");
+  await page.goto("/versions/1.0/docs/quickstart/hugegraph-server/");
+  await expect(page.locator('meta[http-equiv="refresh"]')).toHaveAttribute(
+    "content",
+    /quickstart\/hugegraph\/hugegraph-server/
+  );
+  await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
+    "href",
+    /versions\/1\.0\/docs\/quickstart\/hugegraph\/hugegraph-server\//
+  );
+});
diff --git a/tests/e2e/visual.spec.js b/tests/e2e/visual.spec.js
new file mode 100644
index 000000000..6b1b680f4
--- /dev/null
+++ b/tests/e2e/visual.spec.js
@@ -0,0 +1,25 @@
+const { test, expect } = require("@playwright/test");
+const fs = require("node:fs");
+const path = require("node:path");
+
+const states = [
+  ["en-docs-desktop-light", "/docs/", { width: 1440, height: 900 }, "light"],
+  ["cn-docs-desktop-dark", "/cn/docs/", { width: 1440, height: 900 }, "dark"],
+  ["en-docs-mobile-light", "/docs/", { width: 390, height: 844 }, "light"],
+  ["cn-community-mobile-dark", "/cn/community/", { width: 320, height: 720 }, 
"dark"]
+];
+
+for (const [name, url, viewport, theme] of states) {
+  test(`capture advisory ${name}`, async ({ page }) => {
+    await page.setViewportSize(viewport);
+    await page.addInitScript((value) => localStorage.setItem("td-theme", 
value), theme);
+    await page.goto(url);
+    await expect(page.locator("body")).toBeVisible();
+    const directory = path.join(__dirname, "visual-results");
+    fs.mkdirSync(directory, { recursive: true });
+    await page.screenshot({
+      path: path.join(directory, `${name}.png`),
+      fullPage: true
+    });
+  });
+}

Reply via email to