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 + }); + }); +}
