feat: refine branded operator experience
Add the MBN-branded startup intro, semantic sidebar icons, and refined card-based operator visuals. Strengthen packaged UI and input smoke coverage for the updated experience.
This commit is contained in:
@@ -4,6 +4,18 @@ import { createHash } from "node:crypto";
|
||||
|
||||
const exactUrl = "https://legacy-parity.mbn.local/index.html";
|
||||
const allowedArguments = new Set(["port", "output", "screenshot", "timeout-ms"]);
|
||||
const expectedWorkspaceIconByTabId = Object.freeze({
|
||||
overseas: "overseas",
|
||||
exchange: "exchange",
|
||||
index: "index",
|
||||
kospiIndustry: "kospi",
|
||||
kosdaqIndustry: "kosdaq",
|
||||
comparison: "comparison",
|
||||
theme: "theme",
|
||||
overseasStocks: "overseas-stocks",
|
||||
expert: "expert",
|
||||
tradingHalt: "trading-halt"
|
||||
});
|
||||
const forbiddenCdpMethods = new Set([
|
||||
"Browser.close", "Page.close", "Target.closeTarget", "Runtime.terminateExecution",
|
||||
"Input.dispatchMouseEvent", "Input.dispatchKeyEvent", "Input.insertText"
|
||||
@@ -273,6 +285,39 @@ async function readShell() {
|
||||
bounds.left < document.documentElement.clientWidth &&
|
||||
bounds.top < document.documentElement.clientHeight &&
|
||||
bounds.display !== "none" && bounds.visibility !== "hidden");
|
||||
const icon = container => {
|
||||
const svg = container?.matches?.("svg.workspace-nav-icon[data-icon]")
|
||||
? container
|
||||
: container?.querySelector?.("svg.workspace-nav-icon[data-icon]");
|
||||
const bounds = rectangle(svg);
|
||||
let contentBounds = null;
|
||||
try {
|
||||
const box = svg?.getBBox?.();
|
||||
contentBounds = box ? {
|
||||
x: box.x,
|
||||
y: box.y,
|
||||
width: box.width,
|
||||
height: box.height
|
||||
} : null;
|
||||
} catch (_) {
|
||||
contentBounds = null;
|
||||
}
|
||||
const style = svg ? getComputedStyle(svg) : null;
|
||||
const visible = isVisible(svg, bounds);
|
||||
const painted = Boolean(contentBounds &&
|
||||
contentBounds.width > 0 && contentBounds.height > 0);
|
||||
return {
|
||||
key: svg?.dataset?.icon || null,
|
||||
reference: svg?.querySelector("use")?.getAttribute("href") || null,
|
||||
visible,
|
||||
painted,
|
||||
rendered: Boolean(visible && painted && style &&
|
||||
Number.parseFloat(style.opacity) > 0 &&
|
||||
(style.stroke !== "none" || style.fill !== "none")),
|
||||
bounds,
|
||||
contentBounds
|
||||
};
|
||||
};
|
||||
const operatorShell = document.querySelector(".operator-shell");
|
||||
const workspaceRegion = document.getElementById("workspace-region");
|
||||
const navigationToggle = document.getElementById("workspace-nav-toggle");
|
||||
@@ -282,8 +327,14 @@ async function readShell() {
|
||||
const stockWorkspace = document.getElementById("stock-workspace");
|
||||
const catalogWorkspace = document.getElementById("catalog-workspace");
|
||||
const settingsWorkspace = document.getElementById("settings-workspace");
|
||||
const headingIcon = document.querySelector(".workspace-heading-icon");
|
||||
const splitter = document.getElementById("workspace-splitter");
|
||||
const schedule = document.querySelector(".playlist-panel");
|
||||
const brandWordmark = document.querySelector("img.brand-wordmark");
|
||||
const brandProduct = document.querySelector(".brand-product");
|
||||
const brandDivider = document.querySelector(".brand-divider");
|
||||
const brandWordmarkBounds = rectangle(brandWordmark);
|
||||
const brandDividerBounds = rectangle(brandDivider);
|
||||
const shellBounds = rectangle(operatorShell);
|
||||
const workspaceBounds = rectangle(workspaceRegion);
|
||||
const splitterBounds = rectangle(splitter);
|
||||
@@ -305,6 +356,17 @@ async function readShell() {
|
||||
connectionText: text("playout-connection-state"),
|
||||
playoutStatus: text("playout-status"),
|
||||
title: document.title,
|
||||
brand: {
|
||||
source: brandWordmark?.getAttribute("src") || null,
|
||||
alt: brandWordmark?.getAttribute("alt") || null,
|
||||
complete: brandWordmark?.complete === true,
|
||||
naturalWidth: brandWordmark?.naturalWidth || 0,
|
||||
naturalHeight: brandWordmark?.naturalHeight || 0,
|
||||
rendered: brandWordmarkBounds,
|
||||
visible: isVisible(brandWordmark, brandWordmarkBounds),
|
||||
dividerVisible: isVisible(brandDivider, brandDividerBounds),
|
||||
product: brandProduct?.textContent?.trim() || null
|
||||
},
|
||||
controls: {
|
||||
search: button("stock-search-button"),
|
||||
prepare: button("playout-prepare"),
|
||||
@@ -314,20 +376,24 @@ async function readShell() {
|
||||
},
|
||||
layout: {
|
||||
activeWorkspace: workspaceRegion?.dataset?.activeWorkspace || null,
|
||||
activeMarketTab: workspaceRegion?.dataset?.activeMarketTab || null,
|
||||
navigationAriaExpanded: navigationToggle?.getAttribute("aria-expanded") || null,
|
||||
navigationCollapsedClass: workspaceRegion?.classList.contains("nav-collapsed") === true,
|
||||
navigationExpanded: navigationToggle?.getAttribute("aria-expanded") === "true" &&
|
||||
workspaceRegion?.classList.contains("nav-collapsed") !== true,
|
||||
stockTabCurrent: stockTab?.getAttribute("aria-current") || null,
|
||||
stockTabIcon: icon(stockTab),
|
||||
marketTabCount: marketTabs.length,
|
||||
marketTabsCurrent: marketTabs.map(button => ({
|
||||
tabId: button.dataset.tabId,
|
||||
label: (button.querySelector(".workspace-nav-label")?.textContent || "").trim(),
|
||||
current: button.getAttribute("aria-current") || null,
|
||||
hasIcon: Boolean(button.querySelector("svg")),
|
||||
icon: icon(button),
|
||||
hasLabel: Boolean(button.querySelector(".workspace-nav-label"))
|
||||
})),
|
||||
settingsTabCurrent: settingsTab?.getAttribute("aria-current") || null,
|
||||
settingsTabIcon: icon(settingsTab),
|
||||
headingIcon: icon(headingIcon),
|
||||
currentMenuItemCount: document.querySelectorAll(
|
||||
"#workspace-navigation .workspace-nav-item[aria-current='page']").length,
|
||||
stockWorkspaceVisible: isVisible(stockWorkspace, rectangle(stockWorkspace)) &&
|
||||
@@ -389,6 +455,18 @@ function assertShell(shell) {
|
||||
shell.postMessageGuardInstalled !== true || shell.observedMessagesAfterGuard.length !== 0 || shell.blockedMessages.length !== 0) {
|
||||
fail("KNOWN_FAILURE", "The package shell did not remain rendered DryRun/IDLE with zero guarded native intents.");
|
||||
}
|
||||
if (shell.brand?.source !== "Brand/logo.png" ||
|
||||
shell.brand.alt !== "매일경제TV" ||
|
||||
shell.brand.complete !== true ||
|
||||
shell.brand.naturalWidth !== 176 ||
|
||||
shell.brand.naturalHeight !== 43 ||
|
||||
shell.brand.visible !== true ||
|
||||
shell.brand.dividerVisible !== true ||
|
||||
shell.brand.rendered?.width < 140 ||
|
||||
shell.brand.rendered?.height < 33 ||
|
||||
shell.brand.product !== "V-STOCK") {
|
||||
fail("KNOWN_FAILURE", "The package did not render the verified customer wordmark and V-STOCK lockup.");
|
||||
}
|
||||
const layout = shell.layout;
|
||||
const currentMarkets = layout?.marketTabsCurrent?.filter(tab => tab.current === "page") || [];
|
||||
const navigationExpansionConsistent = layout &&
|
||||
@@ -396,7 +474,28 @@ function assertShell(shell) {
|
||||
(layout.navigationAriaExpanded === "false" && layout.navigationCollapsedClass === true));
|
||||
const commonNavigationOk = layout && navigationExpansionConsistent &&
|
||||
layout.marketTabCount === 10 && layout.currentMenuItemCount === 1 &&
|
||||
layout.marketTabsCurrent.every(tab => tab.hasIcon === true && tab.hasLabel === true);
|
||||
layout.stockTabIcon?.key === "stock-cut" &&
|
||||
layout.stockTabIcon.reference === "#workspace-icon-stock-cut" &&
|
||||
layout.stockTabIcon.rendered === true &&
|
||||
layout.settingsTabIcon?.key === "settings" &&
|
||||
layout.settingsTabIcon.reference === "#workspace-icon-settings" &&
|
||||
layout.settingsTabIcon.rendered === true &&
|
||||
layout.marketTabsCurrent.every(tab => {
|
||||
const expectedIcon = expectedWorkspaceIconByTabId[tab.tabId];
|
||||
return Boolean(expectedIcon) && tab.hasLabel === true &&
|
||||
tab.icon?.key === expectedIcon &&
|
||||
tab.icon.reference === `#workspace-icon-${expectedIcon}` &&
|
||||
tab.icon.rendered === true;
|
||||
});
|
||||
const expectedHeadingIcon = layout?.activeWorkspace === "stock"
|
||||
? "stock-cut"
|
||||
: layout?.activeWorkspace === "settings"
|
||||
? "settings"
|
||||
: expectedWorkspaceIconByTabId[layout?.activeMarketTab];
|
||||
const headingIconOk = Boolean(expectedHeadingIcon) &&
|
||||
layout?.headingIcon?.key === expectedHeadingIcon &&
|
||||
layout.headingIcon.reference === `#workspace-icon-${expectedHeadingIcon}` &&
|
||||
layout.headingIcon.rendered === true;
|
||||
const stockWorkspaceOk = layout?.activeWorkspace === "stock" &&
|
||||
layout.stockTabCurrent === "page" && layout.settingsTabCurrent === "false" &&
|
||||
currentMarkets.length === 0 && layout.stockWorkspaceVisible === true &&
|
||||
@@ -412,7 +511,7 @@ function assertShell(shell) {
|
||||
currentMarkets.length === 1 && layout.stockWorkspaceHidden === true &&
|
||||
layout.catalogWorkspaceVisible === true && layout.settingsWorkspaceHidden === true &&
|
||||
layout.workspaceTitle === currentMarkets[0]?.label;
|
||||
if (!commonNavigationOk ||
|
||||
if (!commonNavigationOk || !headingIconOk ||
|
||||
[stockWorkspaceOk, settingsWorkspaceOk, catalogWorkspaceOk].filter(Boolean).length !== 1) {
|
||||
fail("KNOWN_FAILURE", "The package did not render one internally consistent initial workspace and menu selection.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user