修复官网选购咨询入口与图片失败降级
This commit is contained in:
1 parent
d8224ec5b7
commit
08ffc51894
5 files changed
+463
-89
No files matched your search
@@ -0,0 +1,284 @@
|
||||
/** 官网回归使用真实 CMS 信封和图片字节边界,不替换内部模型或手工触发图片事件。 */
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import {
|
||||
appOrigin,
|
||||
backendOrigin,
|
||||
eventually,
|
||||
loginAccount,
|
||||
loginPassword,
|
||||
scenario,
|
||||
} from "./helpers/console-browser.mjs";
|
||||
|
||||
// 与线上 CMS 的字段形状一致:旧入口仍由 CMS 覆盖,不能只测试默认文案。
|
||||
function cmsContent() {
|
||||
return {
|
||||
header: {
|
||||
navigation: [
|
||||
{ label: "产品服务", href: "#products", is_show: true },
|
||||
{
|
||||
label: "客户自定义方案",
|
||||
href: "https://customer.example.invalid/plan",
|
||||
is_show: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
hero: {
|
||||
title: "为客户保留的自定义标题",
|
||||
buttonLabel: "浏览产品服务",
|
||||
buttonHref: "/login?redirect=%2Fproducts",
|
||||
},
|
||||
products: {
|
||||
items: [
|
||||
{
|
||||
title: "客户自定义云方案",
|
||||
buttonLabel: "查看云服务器",
|
||||
href: "/login?redirect=%2Fcloud-servers",
|
||||
features: [],
|
||||
is_show: true,
|
||||
},
|
||||
{
|
||||
title: "物理服务器",
|
||||
buttonLabel: "查看物理服务器",
|
||||
href: "/login?redirect=%2Fdedicated-servers",
|
||||
features: [],
|
||||
is_show: true,
|
||||
},
|
||||
{
|
||||
title: "机柜托管",
|
||||
buttonLabel: "了解机柜托管",
|
||||
href: "/login?redirect=%2Fcolocations",
|
||||
features: [],
|
||||
is_show: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
phone: "",
|
||||
email: "",
|
||||
address: "",
|
||||
linkGroups: [
|
||||
{
|
||||
title: "产品服务",
|
||||
links: [
|
||||
{ label: "云服务器", href: "/login?redirect=%2Fcloud-servers" },
|
||||
{ label: "机柜托管", href: "/login?redirect=%2Fcolocations" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function officialBoundary(page, content = {}) {
|
||||
const imageErrors = [];
|
||||
await page
|
||||
.context()
|
||||
.route(`${appOrigin}/official-test-images/**`, (route) => {
|
||||
if (route.request().url().endsWith("valid-avatar.svg")) {
|
||||
return route.fulfill({
|
||||
contentType: "image/svg+xml",
|
||||
body: '<svg xmlns="http://www.w3.org/2000/svg" width="2" height="2"><rect width="2" height="2" fill="#0052d9"/></svg>',
|
||||
});
|
||||
}
|
||||
// HTTP 成功但图片不可解码,真实触发 img error;不放宽共享 console 错误门禁。
|
||||
imageErrors.push(new URL(route.request().url()).pathname);
|
||||
return route.fulfill({
|
||||
contentType: "image/png",
|
||||
body: "invalid PNG bytes",
|
||||
});
|
||||
});
|
||||
await page.context().route(`${backendOrigin}/console/v1/**`, (route) => {
|
||||
const path = new URL(route.request().url()).pathname.replace(
|
||||
/^\/console\/v1/,
|
||||
"",
|
||||
);
|
||||
const reply = (data) =>
|
||||
route.fulfill({ json: { status: 200, msg: "", data } });
|
||||
if (path === "/official_site_cms/content") return reply({ content });
|
||||
if (path === "/product/group/first") {
|
||||
return reply({
|
||||
list: [
|
||||
{ id: 1, name: "云服务器" },
|
||||
{ id: 2, name: "物理服务器" },
|
||||
],
|
||||
});
|
||||
}
|
||||
if (path === "/product/group/second") {
|
||||
return reply({ list: [{ id: 11, parent_id: 1, name: "公开云线路" }] });
|
||||
}
|
||||
if (path === "/product") return reply({ list: [], count: 0 });
|
||||
if (path === "/ticket/config") return reply({ ticket_notice_open: 0 });
|
||||
if (["/ticket/type", "/ticket/department"].includes(path))
|
||||
return reply({ list: [] });
|
||||
return route.fallback();
|
||||
});
|
||||
return { imageErrors };
|
||||
}
|
||||
|
||||
for (const [label, destination, content] of [
|
||||
["浏览产品服务", "/products", {}],
|
||||
["浏览产品服务", "/products", cmsContent()],
|
||||
["查看云服务器", "/products/cloud-server", cmsContent()],
|
||||
["查看物理服务器", "/products/dedicated-server", cmsContent()],
|
||||
]) {
|
||||
test(
|
||||
`official purchase CTA opens ${destination} anonymously with ${Object.keys(content).length ? "CMS" : "default"} content`,
|
||||
{ timeout: 30000 },
|
||||
(t) =>
|
||||
scenario(t, {}, async (page) => {
|
||||
await officialBoundary(page, content);
|
||||
await page.goto(`${appOrigin}/official-site`);
|
||||
if (content.hero) {
|
||||
await page
|
||||
.getByRole("heading", { name: content.hero.title, exact: true })
|
||||
.waitFor();
|
||||
assert.equal(
|
||||
await page
|
||||
.getByRole("link", { name: "客户自定义方案", exact: true })
|
||||
.getAttribute("href"),
|
||||
"https://customer.example.invalid/plan",
|
||||
);
|
||||
assert.equal(
|
||||
await page
|
||||
.getByRole("heading", { name: "客户自定义云方案", exact: true })
|
||||
.count(),
|
||||
1,
|
||||
);
|
||||
}
|
||||
await page.getByRole("link", { name: label, exact: true }).click();
|
||||
await eventually(
|
||||
() => new URL(page.url()).pathname,
|
||||
(path) => path === destination,
|
||||
"Public purchase CTA must reach its product route without a login gate",
|
||||
);
|
||||
await page.locator(".public-order-layout").waitFor();
|
||||
assert.equal(await page.locator("#account").count(), 0);
|
||||
return {
|
||||
anonymousDestination: destination,
|
||||
customContentPreserved: !!content.hero,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
test(
|
||||
"official cabinet consultation reaches an implemented support flow",
|
||||
{ timeout: 30000 },
|
||||
(t) =>
|
||||
scenario(t, { loginMode: "password-only" }, async (page) => {
|
||||
const content = cmsContent();
|
||||
content.contact = { is_show: false };
|
||||
content.sectionOrder = ["products", "infrastructure", "reviews"];
|
||||
await officialBoundary(page, content);
|
||||
await page.goto(`${appOrigin}/official-site`);
|
||||
await page
|
||||
.getByRole("heading", { name: "为客户保留的自定义标题", exact: true })
|
||||
.waitFor();
|
||||
assert.equal(
|
||||
await page.locator("#contact").count(),
|
||||
0,
|
||||
"CMS contact visibility must not be overridden",
|
||||
);
|
||||
await page
|
||||
.getByText("机柜托管及定制方案请登录后提交工单咨询", { exact: true })
|
||||
.waitFor();
|
||||
await page
|
||||
.getByRole("link", { name: "了解机柜托管", exact: true })
|
||||
.click();
|
||||
await page.getByRole("button", { name: "登录", exact: true }).waitFor();
|
||||
assert.equal(
|
||||
new URL(page.url()).searchParams.get("redirect"),
|
||||
"/tickets/create",
|
||||
);
|
||||
await page.locator("#account").fill(loginAccount);
|
||||
await page.locator("#password").fill(loginPassword);
|
||||
await page.getByRole("button", { name: "登录", exact: true }).click();
|
||||
await page.waitForURL((url) => url.pathname === "/tickets/create");
|
||||
await page.locator(".ticket-create").waitFor();
|
||||
return {
|
||||
consultationAvailable: true,
|
||||
supportRouteOpened: "/tickets/create",
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
test(
|
||||
"official configurable images fall back after real image decoding failures",
|
||||
{ timeout: 30000 },
|
||||
(t) =>
|
||||
scenario(t, {}, async (page) => {
|
||||
const content = cmsContent();
|
||||
content.brand = {
|
||||
name: "破碎工坊网络",
|
||||
logoUrl: "/official-test-images/logo.png",
|
||||
};
|
||||
content.footer.qrImage = "/official-test-images/qr.png";
|
||||
content.footer.qrCaption = "扫码咨询";
|
||||
content.reviews = {
|
||||
is_show: true,
|
||||
items: [
|
||||
{
|
||||
author: "Sophon",
|
||||
company: "",
|
||||
quote: "保留原有客户评价",
|
||||
avatar: "/official-test-images/avatar.png",
|
||||
is_show: true,
|
||||
},
|
||||
{
|
||||
author: "Ramdisk",
|
||||
company: "",
|
||||
quote: "保留可用客户头像",
|
||||
avatar: "/official-test-images/valid-avatar.svg",
|
||||
is_show: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
const boundary = await officialBoundary(page, content);
|
||||
await page.goto(`${appOrigin}/official-site`);
|
||||
await page.getByText("保留原有客户评价", { exact: true }).waitFor();
|
||||
await eventually(
|
||||
() => [...new Set(boundary.imageErrors)].sort(),
|
||||
(paths) => paths.length === 3,
|
||||
"The browser must request each configured bad image",
|
||||
);
|
||||
await eventually(
|
||||
() => page.locator(".official-brand-mark").count(),
|
||||
(count) => count === 2,
|
||||
"Both failed brand images must use the existing brand mark",
|
||||
);
|
||||
assert.equal(
|
||||
await page.locator(".official-review-avatar").textContent(),
|
||||
"S",
|
||||
);
|
||||
await page
|
||||
.getByText("二维码暂时无法显示,请使用工单咨询", { exact: true })
|
||||
.waitFor();
|
||||
assert.equal(
|
||||
await page.locator(".official-site img").count(),
|
||||
1,
|
||||
"Only the successfully decoded original customer avatar should remain",
|
||||
);
|
||||
assert.equal(
|
||||
await page
|
||||
.getByRole("img", { name: "Ramdisk", exact: true })
|
||||
.evaluate((image) => image.complete && image.naturalWidth > 0),
|
||||
true,
|
||||
);
|
||||
assert.equal(await page.getByText("Sophon", { exact: true }).count(), 1);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
assert.equal(
|
||||
await page.evaluate(
|
||||
() => document.documentElement.scrollWidth <= window.innerWidth,
|
||||
),
|
||||
true,
|
||||
"Image fallbacks and consultation text must fit the mobile viewport",
|
||||
);
|
||||
return {
|
||||
brandFallbacks: 2,
|
||||
customerIdentityPreserved: true,
|
||||
qrReplacedByConsultationText: true,
|
||||
imageFailure: "HTTP 200 with invalid PNG bytes",
|
||||
};
|
||||
}),
|
||||
);
|
||||
Reference in new issue
Block a user