Test Specs IV7 - MZC-CSC/mc-web-console GitHub Wiki
μμ±: 2026-10-16 Β· κΈ°μ€: v07 μ€μΉ ν ν΅ν© κ²μ¦(IV) Β· μλ³Έ: μ¬μ λ³ κ³νμ(Excel) β Test Scenarios Β· IV νλΈ Β· κΈ°λ₯ TC
Playwright(TypeScript) spec μμ€μ
λλ€. μλ리μ€λ§λ€ ## <μλ리μ€λ²νΈ> μΉμ
μ΄ μκ³ μ΅μ»€λ #<μλ리μ€λ²νΈ μλ¬Έμ> μ
λλ€. κ³΅μ© ν¬νΌλ Test-Specs-Common. λΉλ°λ²νΈ λ±μ λ§μ€νΉνμ΅λλ€.
| μλλ¦¬μ€ | νμΌ | μ€ | ν΄μ |
|---|---|---|---|
| IV7_S01 | tests/iv7/IV7_S01.spec.ts |
47 | 21f3a7c6 |
| IV7_S02 | tests/iv7/IV7_S02.spec.ts |
26 | fba5559c |
| IV7_S03 | tests/iv7/IV7_S03.spec.ts |
40 | 3d70ed74 |
| IV7_S04 | tests/iv7/IV7_S04.spec.ts |
53 | 4c18be18 |
| IV7_S05 | tests/iv7/IV7_S05.spec.ts |
33 | 858f38fc |
| IV7_S06 | tests/iv7/IV7_S06.spec.ts |
57 | ec132281 |
| IV7_S07 | tests/iv7/IV7_S07.spec.ts |
65 | b044fbb8 |
| IV7_S08 | tests/iv7/IV7_S08.spec.ts |
30 | a6b7ce95 |
| IV7_S09 | tests/iv7/IV7_S09.spec.ts |
28 | e4e51c4a |
| IV7_S10 | tests/iv7/IV7_S10.spec.ts |
46 | 98f7c330 |
| IV7_S11 | tests/iv7/IV7_S11.spec.ts |
56 | a4126fd6 |
| IV7_S12 | tests/iv7/IV7_S12.spec.ts |
67 | aa108960 |
| IV7_S13 | tests/iv7/IV7_S13.spec.ts |
31 | f0125ce5 |
| IV7_S14 | tests/iv7/IV7_S14.spec.ts |
30 | c1c4f52b |
| IV7_S15 | tests/iv7/IV7_S15.spec.ts |
34 | db2d0c54 |
| IV7_S16 | tests/iv7/IV7_S16.spec.ts |
30 | e1851c70 |
| IV7_S17 | tests/iv7/IV7_S17.spec.ts |
30 | 2f0355ad |
| IV7_S18 | tests/iv7/IV7_S18.spec.ts |
29 | 10c50ab9 |
| IV7_S19 | tests/iv7/IV7_S19.spec.ts |
37 | 8df7af22 |
| IV7_S20 | tests/iv7/IV7_S20.spec.ts |
33 | 9e224647 |
| IV7_S21 | tests/iv7/IV7_S21.spec.ts |
44 | a51ea5a9 |
| IV7_S22 | tests/iv7/IV7_S22.spec.ts |
33 | 40bcbd88 |
| IV7_S23 | tests/iv7/IV7_S23.spec.ts |
91 | 6c91f225 |
| IV7_S24 | tests/iv7/IV7_S24.spec.ts |
38 | eb5eb930 |
| IV7_S25 | tests/iv7/IV7_S25.spec.ts |
63 | 16450ba8 |
| IV7_S26 | tests/iv7/IV7_S26.spec.ts |
28 | 0a66fffe |
| IV7_S27 | tests/iv7/IV7_S27.spec.ts |
50 | 255e0125 |
| IV7_S28 | tests/iv7/IV7_S28.spec.ts |
44 | ee5cfc00 |
| IV7_S29 | tests/iv7/IV7_S29.spec.ts |
35 | 1067e8f8 |
| IV7_S30 | tests/iv7/IV7_S30.spec.ts |
32 | 211db6a2 |
| IV7_S31 | tests/iv7/IV7_S31.spec.ts |
25 | 146ac1b8 |
| IV7_S32 | tests/iv7/IV7_S32.spec.ts |
23 | 6e53aa33 |
| IV7_S33 | tests/iv7/IV7_S33.spec.ts |
28 | d211f549 |
| IV7_S34 | tests/iv7/IV7_S34.spec.ts |
36 | 2b6c55dc |
tests/iv7/IV7_S01.spec.ts Β· 47μ€ Β· ν΄μ 21f3a7c6 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S01: Monitoring: Infra Monitoring Agent μ€μΉ [μ μ, CSPλ³]
// λμ: Infra node (K8s μλ)
// νλ©΄: Monitorings > Config(Monitor Setting, /webconsole/_view/mc011yconfig, iframe) β λμ node μ Monitoring Agent μ΄ Install λ²νΌ
// (Monitoring νλ©΄μμλ node μ 'Go to Config to install agent' λ²νΌμΌλ‘ κ°μ νλ©΄μΌλ‘ μ΄λ. 2026-10-07 νλ‘λΈ νμΈ)
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ μν, Workspace=ws01 / Project=default μ νλ μνμμ μμ. IV3 Infra Running
// μ μ°¨ (μ: AWS κΈ°μ€): 1.Monitorings > Config μ§μ
2.Workload mc1 μ 첫 node(g1-1) Monitoring Agent κ° Not installed μΈμ§ νμΈ
// 3.Monitoring Agent 'Install' ν΄λ¦ β νμΈμ°½ νμΈ(Log Agent λ λλ₯΄μ§ μμ) 4.μμ² μ μ λ° Installing β Installed(SUCCESS) νμΈ
// κΈ°λκ²°κ³Ό: μ€μΉ μμ² μ μ, Monitoring Agent κ° μ€μΉλλ€
// 2026-10-12: νλ©΄μ '첫 Install λ²νΌ' μ΄ μλλΌ λμ node({ng}-1, MON_NODE λ‘ λ³κ²½ κ°λ₯)μ Monitoring Agent μΉΈμ μ§μ ν΄ λλ₯Έλ€ β λ€λ₯Έ Infra/CSP μ node λ₯Ό μλͺ» λλ₯΄μ§ μλλ‘(KT mc9 μΆκ°).
// μ΄λ―Έ μ€μΉλΌ μμΌλ©΄(preexisting) μ€μΉ λ¨κ³λ 건λλ°κ³ μνλ§ κΈ°λ‘νλ€.
// μ ν Infra κ° Config νλ©΄μ μμΌλ©΄ BLOCKED λ‘ skip νλ€.
import { test, expect } from '../../lib/fixtures';
import { activeCsps, tid, infraName, infraNodegroups, DEFAULT_PROJECT } from '../../lib/data';
import { openConfig, waitNodeRunning, agentAction, cellText, classify, settleAgent } from './monitoring';
for (const c of activeCsps(["AWS","Azure","Alibaba","GCP","Tencent","NHN Cloud","NCP","IBM","KT"])) { // μμ
μ μλ CSP: AWS, Azure, Alibaba, GCP, Tencent, NHN Cloud, NCP, IBM, KT
test(`${tid("IV7_S01", c)} ` + "[Infra] Monitoring: Infra μμ΄μ νΈ μ€μΉ", async ({ page, dialogs }) => {
test.setTimeout(30 * 60 * 1000);
const infra = infraName(c);
const node = process.env.MON_NODE || `${infraNodegroups(c)[0]}-1`;
test.info().annotations.push({ type: 'target', description: `Infra node (K8s μλ): infra=${infra} node=${node}` });
const first = await openConfig(page, DEFAULT_PROJECT);
test.skip(!first.text.includes(infra), `BLOCKED: μ ν Infra ${infra} κ° Monitor Setting νλ©΄μ μμ (IV3 Infra Running νμ)`);
const fr = await waitNodeRunning(page, DEFAULT_PROJECT, node);
const monBefore = await settleAgent(page, fr, node, 'Monitoring');
const logBefore = await cellText(fr, node, 'Log');
test.info().annotations.push({ type: 'before', description: `Monitoring: ${monBefore} / Log: ${logBefore}` });
if (classify(monBefore) === 'installed' || classify(monBefore) === 'inactive') {
test.info().annotations.push({ type: 'preexisting', description: `Monitoring Agent μ΄λ―Έ μ€μΉλ¨(${monBefore}) β μ€μΉ λ¨κ³ 건λλ` });
return;
}
expect(classify(monBefore), `μμ: Monitoring Agent Not installed(νμ¬ ${monBefore})`).toMatch(/not-installed|failed/);
if (classify(monBefore) === 'failed') { // μ΄μ μλ νμ (FAILED)μ Uninstall λ‘ μ 리ν λ€ λ€μ μ€μΉ
const u = await agentAction(page, fr, dialogs, node, 'Monitoring', 'Uninstall');
test.info().annotations.push({ type: 'precondition', description: `Monitoring Agent(failed) Uninstall β ${u.state}` });
expect(u.done, `Monitoring Agent Uninstall ν Not installed β λ§μ§λ§ μν: ${u.state}`).toBe(true);
}
const r = await agentAction(page, fr, dialogs, node, 'Monitoring', 'Install');
test.info().annotations.push({ type: 'monitoring-result', description: `Monitoring Agent μ€μΉ κ²°κ³Ό: ${r.cls} (${r.seen.join(' β ')})` });
const logAfter = await cellText(fr, node, 'Log');
test.info().annotations.push({ type: 'after', description: `Monitoring: ${r.state} / Log: ${logAfter}` });
expect.soft(logAfter, 'Log Agent μν λ³ν μμ').toBe(logBefore);
expect(r.done, `Monitoring Agent μ€μΉ μλ£(Installed) β λ§μ§λ§ μν: ${r.state}`).toBe(true);
});
}tests/iv7/IV7_S02.spec.ts Β· 26μ€ Β· ν΄μ fba5559c Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S02: Monitoring: K8s λͺ¨λν°λ§ μ€μΉ [μ μ, CSPλ³]
// λμ: K8s cluster / K8s node (Infra μλ)
// νλ©΄: Monitorings > Config(Monitor Setting, iframe, Namespace=Project) β K8s {cluster} μ node β Monitoring Agent 'Install'
// μμ μν: admin λ‘κ·ΈμΈ μν, Workspace=ws01 / Project=p1 μ ν μνμμ μμ. IV3 K8s Ready
// μ μ°¨ (μ: AWS κΈ°μ€): 1.Monitoring(K8s ν)μμ k1d μ node κ° 'agent not installed' μΈμ§ νμΈ 2.Config νλ©΄ K8s k1d μ node Monitoring Agent 'Install' ν΄λ¦
// 3.μ€μΉ μμ² μ μ(μλ¦Ό λλ μν λ³ν) νμΈ
// κΈ°λκ²°κ³Ό: μ€μΉ μμ² μ μ
// μ ν K8s(k{n}) κ° Config νλ©΄μ μμΌλ©΄ BLOCKED λ‘ skip νλ€.
// λμ K8s: κΈ°λ³Έ k{n}(IV3_S13 Workflow μ {prefix}-{csp} μ΄λ¦μ k1-aws λΌ λ€λ₯Ό μ μμ) β K8S_CLUSTER λ‘ λ³κ²½(v07 AWS: k1d)
import { test } from '../../lib/fixtures';
import { activeCsps, tid, k8sName, k8sProject } from '../../lib/data';
import { openConfig, installFirstAgent } from './monitoring';
for (const c of activeCsps(["AWS","Azure","Alibaba","GCP","Tencent","NHN Cloud","NCP","IBM","KT"])) { // μμ
μ μλ CSP: AWS, Azure, Alibaba, GCP, Tencent, NHN Cloud, NCP, IBM, KT
test(`${tid("IV7_S02", c)} ` + "[K8s] Monitoring: K8s λͺ¨λν°λ§ μ€μΉ", async ({ page, dialogs }) => {
test.setTimeout(10 * 60 * 1000);
const cluster = process.env.K8S_CLUSTER || k8sName(c);
test.info().annotations.push({ type: 'target', description: `K8s cluster/node (Infra μλ): cluster=${cluster}, project=${k8sProject(c)}` });
const { fr, text } = await openConfig(page, k8sProject(c));
test.skip(!text.includes(cluster), `BLOCKED: μ ν K8s ${cluster} κ° Monitor Setting νλ©΄μ μμ (IV3 K8s Ready, Project ${k8sProject(c)} νμ)`);
// ν΄λ¬μ€ν°κ° μ¬λ¬ κ°λ©΄(k1-aws λ node 0) λμ ν΄λ¬μ€ν° ꡬμμ Install μ λλ¬μΌ νλ€ β 'No nodes' μΈ ν΄λ¬μ€ν°λ₯Ό 건λλ°κ³ 첫 Install μ μ¬μ©
await installFirstAgent(page, fr, dialogs);
});
}tests/iv7/IV7_S03.spec.ts Β· 40μ€ Β· ν΄μ 3d70ed74 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S03: [Infra] Monitoring: Log Agent μ€μΉ [μ μ, CSPλ³]
// λμ: Infra node (K8s μλ)
// νλ©΄: Monitorings > Config(Monitor Setting, /webconsole/_view/mc011yconfig, iframe) β λμ node μ Log Agent μ΄ Install λ²νΌ
// μ μ : admin(mcmp) λ‘κ·ΈμΈ μν, Workspace=ws01 / Project=default. IV3 Infra mc{n} Running
// μ μ°¨(AWS): 1.Monitorings > Config μ§μ
2.Workload mc1 μ node g1-1 Log Agent κ° Not installed μΈμ§ νμΈ(FAILED λ± μ΄μ μλ νμ μ΄ μμΌλ©΄ Uninstall λ‘ μ 리)
// 3.Log Agent 'Install' ν΄λ¦(Monitoring Agent λ λλ₯΄μ§ μμ) β νμΈμ°½ νμΈ 4.μμ² μ μ λ° Installing β Installed(SUCCESS) νμΈ 5.Monitoring Agent μνλ μμ λμ κ°μμ§ νμΈ
// κΈ°λκ²°κ³Ό: Log Agent μ€μΉ μμ²μ΄ μ μλκ³ Log Agent λ§ μ€μΉλλ€(Monitoring Agent μν λ³ν μμ)
// μν 문ꡬ: Not installed / INSTALLING Installingβ¦ / SUCCESS(λλ Installed) / FAILED / SERVICE_INACTIVE (+ Uninstall λ²νΌ). FAILED λ μ€μΉ μ€ν¨μ΄λ©° Uninstall λ‘ μ 리 κ°λ₯
// μ ν Infra κ° Config νλ©΄μ μμΌλ©΄ BLOCKED λ‘ skip. λμ node λ 첫 node({ng}-1), MON_NODE νκ²½λ³μλ‘ λ³κ²½ κ°λ₯(NODE λ node μ€ν κ²½λ‘ νκ²½λ³μλΌ μ°μ§ μλλ€)
import { test, expect } from '../../lib/fixtures';
import { activeCsps, tid, infraName, infraNodegroups, DEFAULT_PROJECT } from '../../lib/data';
import { openConfig, waitNodeRunning, agentAction, cellText, classify, settleAgent } from './monitoring';
for (const c of activeCsps(["AWS","Azure","Alibaba","GCP","Tencent","NHN Cloud","NCP","IBM","KT"])) {
test(`${tid("IV7_S03", c)} ` + "[Infra] Monitoring: Log Agent μ€μΉ", async ({ page, dialogs }) => {
test.setTimeout(30 * 60 * 1000);
const infra = infraName(c);
const node = process.env.MON_NODE ||`${infraNodegroups(c)[0]}-1`;
test.info().annotations.push({ type: 'target', description: `Infra node (K8s μλ): infra=${infra} node=${node}` });
const first = await openConfig(page, DEFAULT_PROJECT);
test.skip(!first.text.includes(infra), `BLOCKED: μ ν Infra ${infra} κ° Monitor Setting νλ©΄μ μμ (IV3 Infra Running νμ)`);
const fr = await waitNodeRunning(page, DEFAULT_PROJECT, node);
const monBefore = await cellText(fr, node, 'Monitoring');
let logBefore = await settleAgent(page, fr, node, 'Log');
test.info().annotations.push({ type: 'before', description: `Monitoring: ${monBefore} / Log: ${logBefore}` });
// μ μ μ€λΉ: μ€μΉλΌ μκ±°λ μ€ν¨ νμ μ΄ μμΌλ©΄ Uninstall λ‘ Not installed λ‘ λ§μΆλ€
if (!/not-installed/.test(classify(logBefore))) {
const u = await agentAction(page, fr, dialogs, node, 'Log', 'Uninstall');
test.info().annotations.push({ type: 'precondition', description: `Log Agent(${classify(logBefore)}) Uninstall β ${u.state}` });
expect(u.done, `Log Agent Uninstall ν Not installed β λ§μ§λ§ μν: ${u.state}`).toBe(true);
}
const r = await agentAction(page, fr, dialogs, node, 'Log', 'Install');
test.info().annotations.push({ type: 'log-result', description: `Log Agent μ€μΉ κ²°κ³Ό: ${r.cls} (${r.seen.join(' β ')})` });
const monAfter = await cellText(fr, node, 'Monitoring');
test.info().annotations.push({ type: 'after', description: `Monitoring: ${monAfter} / Log: ${r.state}` });
expect.soft(monAfter, 'Monitoring Agent μν λ³ν μμ').toBe(monBefore);
expect(r.done, `Log Agent μ€μΉ μλ£(Installed) β λ§μ§λ§ μν: ${r.state}`).toBe(true);
});
}tests/iv7/IV7_S04.spec.ts Β· 53μ€ Β· ν΄μ 4c18be18 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S04: [K8s] Monitoring: Log Agent μ€μΉ [μ μ, CSPλ³]
// λμ: K8s cluster / K8s node (Infra μλ)
// νλ©΄: Monitorings > Config(Monitor Setting, iframe, Namespace=Project) β K8s {cluster} μ node β Log Agent μ΄ Install λ²νΌ
// μ μ : admin λ‘κ·ΈμΈ μν, Workspace=ws01 / Project=p{n}. IV3 K8s Active. AWS: Project p1 / k1d / NodeGroup g1s
// μ μ°¨(AWS): 1.K8s Workloads μμ k1d NodeGroup g1s μ node νμΈ 2.Monitorings > Config(p1) μ K8s k1d μμ κ·Έ node νμ Log Agent νμΈ
// 3.Log Agent κ° Not installed κ° μλλ©΄(μ΄μ μλ/μ€μΉ) Uninstall λ‘ μ 리(μ μ μ€λΉ) 4.Log Agent 'Install' ν΄λ¦(Monitoring Agent λ λλ₯΄μ§ μμ) β νμΈ
// 5.Log Agent Installing β Installed νμΈ 6.Monitoring Agent μνλ μμ λμ κ°μμ§ νμΈ
// κΈ°λκ²°κ³Ό: Log Agent μ€μΉ μμ²μ΄ μ μλκ³ Log Agent λ§ μ€μΉλλ€(Monitoring Agent μν λ³ν μμ)
// λμ ν΄λ¬μ€ν°/NodeGroup: K8S_CLUSTER(κΈ°λ³Έ k{n}), K8S_NG(κΈ°λ³Έ g{n}s) β v07 AWS: k1d / g1s
import { test, expect } from '../../lib/fixtures';
import { activeCsps, tid, k8sName, k8sProject } from '../../lib/data';
import { loginAsAdmin } from '../../lib/ui';
import { gotoK8s, selectNodeGroup } from '../../lib/iv3-k8s';
import { openConfig, waitNodeRunning, agentAction, cellText, classify, settleAgent } from './monitoring';
for (const c of activeCsps(["AWS","Azure","Alibaba","GCP","Tencent","NHN Cloud","NCP","IBM","KT"])) {
test(`${tid("IV7_S04", c)} ` + "[K8s] Monitoring: Log Agent μ€μΉ", async ({ page, dialogs }) => {
test.setTimeout(40 * 60 * 1000);
const prj = k8sProject(c);
const cluster = process.env.K8S_CLUSTER || k8sName(c);
const ngName = process.env.K8S_NG || `g${c.n}s`;
await loginAsAdmin(page);
await gotoK8s(page, prj);
const st = await selectNodeGroup(page, cluster, ngName);
const node = st.nodes[0];
test.info().annotations.push({ type: 'target', description: `K8s cluster/node (Infra μλ): ${prj} / ${cluster} / NodeGroup ${ngName} / node ${node ?? '(μμ)'}` });
test.skip(!node, `BLOCKED: NodeGroup ${ngName} μ node μμ`);
// K8s λͺ©λ‘μ νλ©΄ μ λͺ©λ³΄λ€ λ¦κ² μ±μμ§λ€ β μ΅λ 3ν λ€μ μ΄μ΄ νμΈ
let seen = false;
for (let i = 0; i < 3 && !seen; i++) {
const o = await openConfig(page, prj);
seen = o.text.includes(cluster) && o.text.includes(node);
if (!seen) await page.waitForTimeout(15_000);
}
test.skip(!seen, `BLOCKED: ${cluster} / ${node} κ° Monitor Setting νλ©΄μ μμ`);
const fr = await waitNodeRunning(page, prj, node);
const monBefore = await settleAgent(page, fr, node, 'Monitoring');
const logBefore = await settleAgent(page, fr, node, 'Log');
test.info().annotations.push({ type: 'before', description: `Monitoring: ${monBefore} / Log: ${logBefore}` });
if (classify(logBefore) !== 'not-installed') {
const u = await agentAction(page, fr, dialogs, node, 'Log', 'Uninstall');
test.info().annotations.push({ type: 'precondition', description: `Log Agent(${classify(logBefore)}) Uninstall β ${u.state}` });
expect(u.done, `Log Agent Uninstall ν Not installed β λ§μ§λ§ μν: ${u.state}`).toBe(true);
}
const r = await agentAction(page, fr, dialogs, node, 'Log', 'Install');
test.info().annotations.push({ type: 'log-result', description: `Log Agent μ€μΉ κ²°κ³Ό: ${r.cls} (${r.seen.join(' β ')})` });
const monAfter = await cellText(fr, node, 'Monitoring');
test.info().annotations.push({ type: 'after', description: `Monitoring: ${monAfter} / Log: ${r.state}` });
expect.soft(monAfter, 'Monitoring Agent μν λ³ν μμ').toBe(monBefore);
expect(r.done, `Log Agent μ€μΉ μλ£(Installed) β λ§μ§λ§ μν: ${r.state}`).toBe(true);
});
}tests/iv7/IV7_S05.spec.ts Β· 33μ€ Β· ν΄μ 858f38fc Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S05: [Infra] Monitoring: Monitoring Agent + Log Agent λ λ€ μ€μΉ [μ μ, CSPλ³]
// λμ: Infra node (K8s μλ)
// νλ©΄: Monitorings > Config(Monitor Setting, iframe) β λμ node μ Monitoring Agent μ΄ Install, Log Agent μ΄ Install
// μ μ : admin(mcmp) λ‘κ·ΈμΈ μν, Workspace=ws01 / Project=default. IV3 Infra mc{n} Running, λ μμ΄μ νΈκ° λͺ¨λ Not installed μΈ node νμ(AWS: g1-2)
// μ μ°¨(AWS): 1.Monitorings > Config μ§μ
2.Workload mc1 μ λ μμ΄μ νΈκ° λͺ¨λ Not installed μΈ node(g1-2) νμΈ 3.Monitoring Agent 'Install' β νμΈ 4.Log Agent 'Install' β νμΈ
// 5.λ μμ΄μ νΈ λͺ¨λ Installing β Installed(Uninstall λ²νΌ) νμΈ(μ½ 3λΆ)
// κΈ°λκ²°κ³Ό: λ μμ΄μ νΈ μ€μΉ μμ²μ΄ κ°κ° μ μλκ³ λͺ¨λ Installed κ° λλ€
// λμ node λ MON_NODE νκ²½λ³μλ‘ μ§μ (κΈ°λ³Έ {ng}-2). λ μμ΄μ νΈκ° μ΄λ―Έ μ€μΉ/μΌλΆ μ€μΉλ node λ©΄ BLOCKED λ‘ skip
import { test, expect } from '../../lib/fixtures';
import { activeCsps, tid, infraName, infraNodegroups, DEFAULT_PROJECT } from '../../lib/data';
import { openConfig, waitNodeRunning, agentAction, cellText, classify, settleAgent } from './monitoring';
for (const c of activeCsps(["AWS","Azure","Alibaba","GCP","Tencent","NHN Cloud","NCP","IBM","KT"])) {
test(`${tid("IV7_S05", c)} ` + "[Infra] Monitoring: Monitoring Agent + Log Agent λ λ€ μ€μΉ", async ({ page, dialogs }) => {
test.setTimeout(30 * 60 * 1000);
const infra = infraName(c);
const node = process.env.MON_NODE ||`${infraNodegroups(c)[0]}-2`;
test.info().annotations.push({ type: 'target', description: `Infra node (K8s μλ): infra=${infra} node=${node}` });
const first = await openConfig(page, DEFAULT_PROJECT);
test.skip(!first.text.includes(infra), `BLOCKED: μ ν Infra ${infra} κ° Monitor Setting νλ©΄μ μμ (IV3 Infra Running νμ)`);
const fr = await waitNodeRunning(page, DEFAULT_PROJECT, node);
const mon0 = await settleAgent(page, fr, node, 'Monitoring');
const log0 = await settleAgent(page, fr, node, 'Log');
test.info().annotations.push({ type: 'before', description: `Monitoring: ${mon0} / Log: ${log0}` });
test.skip(!(classify(mon0) === 'not-installed' && classify(log0) === 'not-installed'), `BLOCKED: ${node} μ λ μμ΄μ νΈκ° λͺ¨λ Not installed κ° μλ`);
const m = await agentAction(page, fr, dialogs, node, 'Monitoring', 'Install');
const l = await agentAction(page, fr, dialogs, node, 'Log', 'Install');
test.info().annotations.push({ type: 'after', description: `Monitoring: ${await cellText(fr, node, 'Monitoring')} / Log: ${await cellText(fr, node, 'Log')}` });
expect.soft(m.done, `Monitoring Agent μ€μΉ μλ£ β λ§μ§λ§ μν: ${m.state}`).toBe(true);
expect.soft(l.done, `Log Agent μ€μΉ μλ£ β λ§μ§λ§ μν: ${l.state}`).toBe(true);
});
}tests/iv7/IV7_S06.spec.ts Β· 57μ€ Β· ν΄μ ec132281 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S06: [K8s] Monitoring: Monitoring Agent + Log Agent λ λ€ μ€μΉ [μ μ, CSPλ³]
// λμ: K8s cluster / K8s node (Infra μλ)
// νλ©΄: Monitorings > Config(Monitor Setting, iframe, Namespace=Project) β K8s {cluster} μ node β Monitoring Agent μ΄ Install, Log Agent μ΄ Install
// μ μ : admin λ‘κ·ΈμΈ μν, Workspace=ws01 / Project=p{n}. IV3 K8s Active. AWS: Project p1 / k1d / NodeGroup g1s(μ¬μ©μ μ§μ 2026-10-08)
// μ μ°¨(AWS): 1.K8s Workloads μμ k1d NodeGroup g1s μ node νμΈ 2.Monitorings > Config(p1) μ K8s k1d μμ κ·Έ node ν νμΈ
// 3.Monitoring Agent κ° μ΄λ―Έ μ€μΉλΌ μμΌλ©΄(IV7_S02) λ¨Όμ Uninstall λ‘ Not installed μνλ₯Ό λ§λ λ€(μ μ μ€λΉ) 4.Monitoring Agent 'Install' β νμΈ 5.Log Agent 'Install' β νμΈ
// 6.λ μμ΄μ νΈ λͺ¨λ Installing β Installed νμΈ
// κΈ°λκ²°κ³Ό: λ μμ΄μ νΈ μ€μΉ μμ²μ΄ κ°κ° μ μλκ³ λͺ¨λ Installed κ° λλ€
// λμ ν΄λ¬μ€ν°/NodeGroup: K8S_CLUSTER(κΈ°λ³Έ k{n}), K8S_NG(κΈ°λ³Έ g{n}s) β v07 AWS: k1d / g1s
import { test, expect } from '../../lib/fixtures';
import { activeCsps, tid, k8sName, k8sProject } from '../../lib/data';
import { loginAsAdmin } from '../../lib/ui';
import { gotoK8s, selectNodeGroup } from '../../lib/iv3-k8s';
import { openConfig, waitNodeRunning, agentAction, cellText, classify, settleAgent } from './monitoring';
for (const c of activeCsps(["AWS","Azure","Alibaba","GCP","Tencent","NHN Cloud","NCP","IBM","KT"])) {
test(`${tid("IV7_S06", c)} ` + "[K8s] Monitoring: Monitoring Agent + Log Agent λ λ€ μ€μΉ", async ({ page, dialogs }) => {
test.setTimeout(40 * 60 * 1000);
const prj = k8sProject(c);
const cluster = process.env.K8S_CLUSTER || k8sName(c);
const ngName = process.env.K8S_NG || `g${c.n}s`;
await loginAsAdmin(page);
await gotoK8s(page, prj);
const st = await selectNodeGroup(page, cluster, ngName);
const node = st.nodes[0];
test.info().annotations.push({ type: 'target', description: `K8s cluster/node (Infra μλ): ${prj} / ${cluster} / NodeGroup ${ngName} / node ${node ?? '(μμ)'}` });
test.skip(!node, `BLOCKED: NodeGroup ${ngName} μ node μμ`);
// K8s λͺ©λ‘μ νλ©΄ μ λͺ©λ³΄λ€ λ¦κ² μ±μμ§λ€(2026-10-08: 첫 λ‘λμμ 'Monitor Setting Namespace: p1 Refresh' λ§ λ³΄μ) β μ΅λ 3ν λ€μ μ΄μ΄ νμΈ
let seen = false;
for (let i = 0; i < 3 && !seen; i++) {
const o = await openConfig(page, prj);
seen = o.text.includes(cluster) && o.text.includes(node);
if (!seen) await page.waitForTimeout(15_000);
}
test.skip(!seen, `BLOCKED: ${cluster} / ${node} κ° Monitor Setting νλ©΄μ μμ`);
const fr = await waitNodeRunning(page, prj, node);
const mon = await settleAgent(page, fr, node, 'Monitoring');
const log = await settleAgent(page, fr, node, 'Log');
test.info().annotations.push({ type: 'before', description: `Monitoring: ${mon} / Log: ${log}` });
// μ μ μ€λΉ: μ΄λ―Έ μ€μΉλ μμ΄μ νΈλ Uninstall ν΄μ λ λ€ Not installed λ‘ λ§μΆλ€
for (const kind of ['Monitoring', 'Log'] as const) {
const t = kind === 'Monitoring' ? mon : log;
// 'Failed Install'(K8s μ€μΉ μ€ν¨, 2026-10-15 GKE)μ²λΌ Install λ²νΌλ§ μμΌλ©΄ Uninstall μμ΄ λ°λ‘ Install λ¨κ³λ‘
if (classify(t) !== 'not-installed' && /Uninstall/.test(t)) {
const u = await agentAction(page, fr, dialogs, node, kind, 'Uninstall');
test.info().annotations.push({ type: 'precondition', description: `${kind} Agent Uninstall β ${u.state}` });
expect(u.done, `${kind} Agent Uninstall ν Not installed β λ§μ§λ§ μν: ${u.state}`).toBe(true);
}
}
const m = await agentAction(page, fr, dialogs, node, 'Monitoring', 'Install');
const l = await agentAction(page, fr, dialogs, node, 'Log', 'Install');
test.info().annotations.push({ type: 'after', description: `Monitoring: ${await cellText(fr, node, 'Monitoring')} / Log: ${await cellText(fr, node, 'Log')}` });
expect.soft(m.done, `Monitoring Agent μ€μΉ μλ£ β λ§μ§λ§ μν: ${m.state}`).toBe(true);
expect.soft(l.done, `Log Agent μ€μΉ μλ£ β λ§μ§λ§ μν: ${l.state}`).toBe(true);
});
}tests/iv7/IV7_S07.spec.ts Β· 65μ€ Β· ν΄μ b044fbb8 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S07: TC-OBS-007 λͺ¨λν°λ§ νλ¬κ·ΈμΈ(item) λ±λ‘ (Monitoring Agent) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-AGENT-04
// νλ©΄: Monitorings > Config β Agent μ€μΉ μλ£ node β λ©νΈλ¦(νλ¬κ·ΈμΈ) ν κΈ ν
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ node Monitoring Agent SUCCESS(TC-OBS-AGENT-03), node Running
// μ μ°¨: 1.μ€μΉ μλ£ node μ ν 2.λ±λ‘ item(GET β¦/node/{nodeId}/item)Β·μ¬μ© κ°λ₯ νλ¬κ·ΈμΈ(GET /api/o11y/monitoring/plugins) 3.λΉνμ± νλ¬κ·ΈμΈ ON 4.λ±λ‘(POST β¦/node/{nodeId}/item) 5.item λͺ©λ‘ κ°±μ 6.(μ볡) μΌ νλ¬κ·ΈμΈ OFF(DELETE β¦/item/{itemSeq})
// κΈ°λκ²°κ³Ό: νλ¬κ·ΈμΈ λ±λ‘ 2xx, item λͺ©λ‘(seq, pluginName, state)
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν.
// 2026-10-16 μμ (rf-obs2 νμ, μ€ν μ ): νλ©΄ νμΈ κ²°κ³Ό node ν(<td>) ν΄λ¦ β ν μλ 'Monitoring Metrics β {node}' ν¨λ(νλ¬κ·ΈμΈ 11κ° μ²΄ν¬λ°μ€ cpuβ¦system + 'Apply',
// μλ΄ 'Enables the selected metrics and disables the rest.'). νλ³ ν κΈμ΄ μλλΌ Apply ν λ²μ μ ν μ§ν©μ λ°μ β μ΄μ μ ν μνλ₯Ό κΈ°λ‘νκ³ νλλ§ μΆκ°ν΄ Apply, μ볡μ μ΄μ μ§ν©μΌλ‘ λ€μ Apply.
// 2026-10-16 보μ(μ¬μ©μ μ§μ): λΉνμ± νλ¬κ·ΈμΈμ΄ μμΌλ©΄ νλλ₯Ό λκ³ Apply β λ€μ μΌμ Apply β item λͺ©λ‘μ λ±λ‘ νμΈ(λ μν = μλλλ‘ λͺ¨λ μΌμ§)
// node κ° Suspended μ΄λ©΄ item μ‘°νκ° μκ³ μ²΄ν¬κ° λͺ¨λ λΉμ΄ 보μ¬(Apply μ μ λΆ λκ² λ¨) μ€ννμ§ μλλ€ β Running node(OBS_WRITE_NODE) νμ. Apply μ΄ν API(POST/DELETE item) λ λ―Έκ²μ¦
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, clean, OBS_NS, OBS_INFRA } from '../../lib/iv7-obs';
test('IV7_S07 TC-OBS-007 λͺ¨λν°λ§ νλ¬κ·ΈμΈ(item) λ±λ‘', async ({ page }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const node = process.env.OBS_WRITE_NODE ?? '';
test.skip(!node, 'OBS_WRITE_NODE λ―Έμ§μ (κ³΅μ© node 보νΈ)');
const items = collectResponses(page, new RegExp(`/api/o11y/monitoring/${OBS_NS}/${OBS_INFRA}/node/${node}/item`));
await loginAsAdmin(page);
const f = await openObs(page, 'config');
const row = f.locator('tr').filter({ has: f.locator('td', { hasText: new RegExp(`^${node}$`) }) }).first();
const rowText = clean(await row.innerText());
test.info().annotations.push({ type: 'row', description: rowText });
test.skip(!/Running/.test(rowText), `BLOCKED: ${node} κ° Running μλ(${rowText}) β νλ¬κ·ΈμΈ λ°μμ VM μ μ νμ`);
await row.locator('td').first().click();
await expect(f.getByRole('button', { name: 'Apply', exact: true })).toBeVisible({ timeout: 30_000 });
await expect.poll(() => items.filter((i) => i.method === 'GET').length, { message: 'item λͺ©λ‘ GET', timeout: 30_000 }).toBeGreaterThan(0);
const before: any[] = items.filter((i) => i.method === 'GET').at(-1)!.json?.data ?? [];
test.info().annotations.push({ type: 'items-before', description: JSON.stringify(before).slice(0, 600) });
const boxes = f.locator('label').filter({ has: f.locator('input[type="checkbox"]') });
const names = (await boxes.allInnerTexts()).map((s) => s.trim());
const checked = await boxes.locator('input[type="checkbox"]').evaluateAll((els) => els.map((e) => (e as HTMLInputElement).checked));
const apply = async (label: string) => {
const n0 = items.length;
await f.getByRole('button', { name: 'Apply', exact: true }).click();
await expect.poll(() => items.slice(n0).some((i) => i.method !== 'GET' && i.status < 300), { message: `${label} 2xx`, timeout: 60_000 }).toBe(true);
test.info().annotations.push({ type: label, description: items.slice(n0).filter((i) => i.method !== 'GET').map((i) => `${i.status} ${i.method} ${i.url.split('/api/')[1]}`).join(' | ').slice(0, 600) });
};
let offIdx = checked.findIndex((c, i) => !c && !/nvidia/.test(names[i]));
// 2026-10-16 보μ(μ¬μ©μ μ§μ): λͺ¨λ νλ¬κ·ΈμΈμ΄ μΌμ Έ μμΌλ©΄(λΉνμ± μμ) νλλ₯Ό λ¨Όμ λκ³ Apply β κ·Έ νλ¬κ·ΈμΈμ λ€μ μΌμ λ±λ‘λλμ§ νμΈνλ€.
// λ νλ¬κ·ΈμΈμ λ§μ§λ§μ μΌ μνλ‘ λλλ―λ‘ μλ μν(λͺ¨λ μΌμ§)λ‘ λμκ°λ€.
let restoreOff = true; // μλ κΊΌμ Έ μλ κ²μ μΌ°μΌλ©΄ λ§μ§λ§μ λ€μ λλ€
if (offIdx < 0) {
offIdx = names.findIndex((n) => !/nvidia/.test(n) && !/^cpu$/i.test(n)); // cpu λ κΈ°λ³Έ μμ§μ΄λΌ νΌν¨
expect(offIdx, 'λκ³ λ€μ μΌ€ νλ¬κ·ΈμΈ').toBeGreaterThanOrEqual(0);
await boxes.nth(offIdx).locator('input').uncheck();
await apply('pre-off');
restoreOff = false;
}
const name = names[offIdx];
await boxes.nth(offIdx).locator('input').check();
await apply('register');
// λ±λ‘ νμΈ: item λͺ©λ‘μ λ€μ μ½μ΄ κ·Έ νλ¬κ·ΈμΈμ΄ μλμ§
await f.locator('tr').filter({ has: f.locator('td', { hasText: new RegExp(`^${node}$`) }) }).first().locator('td').first().click();
await expect.poll(() => (items.filter((i) => i.method === 'GET').at(-1)?.json?.data ?? []).some((x: any) => new RegExp(name, 'i').test(x.pluginName ?? x.name ?? JSON.stringify(x))),
{ message: `item λͺ©λ‘μ ${name}`, timeout: 60_000 }).toBe(true);
test.info().annotations.push({ type: 'items-after', description: JSON.stringify(items.filter((i) => i.method === 'GET').at(-1)?.json?.data ?? []).slice(0, 600) });
if (restoreOff) { // (μ볡) μλ κΊΌμ Έ μλ νλ¬κ·ΈμΈμ λ€μ λ
await boxes.nth(offIdx).locator('input').uncheck();
await apply('restore-off');
}
});tests/iv7/IV7_S08.spec.ts Β· 30μ€ Β· ν΄μ a6b7ce95 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S08: TC-OBS-008 Monitoring Agent μ κ±° (Uninstall) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-AGENT-05
// νλ©΄: Monitorings > Config β node ν Monitoring Agent μ Uninstall β νμΈ
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. μ€μΉλ node(OBS_WRITE_NODE β λ€λ₯Έ μΈμ
μ΄ μ°λ node λ μ§μ κΈμ§)
// μ μ°¨: 1.node ν Monitoring Agent Uninstall β νμΈ 2.μ κ±° νΈμΆ(DELETE /api/o11y/monitoring/{ns}/{infra}/node/{node}) 3.λ―Έλ±λ‘ μν μ ν 4.(μ볡) μ¬μ€μΉλ IV7_S01 μ μ°¨
// κΈ°λκ²°κ³Ό: μ κ±° μλ΅ 2xx, node λ―Έλ±λ‘ μν
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν. μ
λ ν°λ IV7_S03 κ³Ό κ°μ monitoring.ts agentAction(κ²μ¦λ¨)
// 2026-10-16 μμ (rf-obs2): node κ° Running μ΄ μλλ©΄ BLOCKED skip μΆκ° β Suspended node μμ Uninstall νλ©΄ VM μ μ μ€ν¨λ‘ FAILED κ° λ¨λλ€(mc9 g9-1 μ¬λ‘).
// 2026-10-16 μμ λμ ν보 mc1/g1-2(λ μμ΄μ νΈ SERVICE_INACTIVE=μ€μΉλ¨, Suspended)λ Resume μ΄ νμν΄ λ―Έμ€ν
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, OBS_NS, OBS_INFRA } from '../../lib/iv7-obs';
import { agentAction, cellText, rowStatus } from './monitoring';
test('IV7_S08 TC-OBS-008 Monitoring Agent μ κ±° (Uninstall)', async ({ page, dialogs }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const node = process.env.OBS_WRITE_NODE ?? '';
test.skip(!node, 'OBS_WRITE_NODE λ―Έμ§μ (κ³΅μ© node 보νΈ)');
const del = collectResponses(page, new RegExp(`/api/o11y/monitoring/${OBS_NS}/${OBS_INFRA}/node/${node}`));
await loginAsAdmin(page);
const f = page.frameLocator('iframe').first();
await openObs(page, 'config');
const st = await rowStatus(f, node);
test.info().annotations.push({ type: 'node-status', description: `${node}:${st || 'ν μμ'} / Monitoring: ${await cellText(f, node, 'Monitoring')} / Log: ${await cellText(f, node, 'Log')}` });
test.skip(st !== 'Running', `BLOCKED: ${node} κ° Running μλ(${st || 'ν μμ'}) β μμ΄μ νΈ μ κ±°λ VM μ μ νμ('Cannot connect to the VM β it must be running')`);
await agentAction(page, f, dialogs, node, 'Monitoring', 'Uninstall');
test.info().annotations.push({ type: 'api', description: del.map((d) => `${d.status} ${d.method} ${d.url}`).join(' | ') });
expect(await cellText(f, node, 'Monitoring')).toMatch(/NOT[_ ]INSTALLED|Install/i);
});tests/iv7/IV7_S09.spec.ts Β· 28μ€ Β· ν΄μ e4e51c4a Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S09: TC-OBS-009 Log Agent μ κ±° (Uninstall) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-AGENT-05(Log)
// νλ©΄: Monitorings > Config β node ν Log Agent μ Uninstall β νμΈ
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. Log Agent μ€μΉ node(OBS_WRITE_NODE)
// μ μ°¨: 1.node ν Log Agent Uninstall β νμΈ 2.μ κ±° νΈμΆ 3.λ―Έλ±λ‘ μν 4.(μ볡) μ¬μ€μΉλ IV7_S03 μ μ°¨
// κΈ°λκ²°κ³Ό: μ κ±° μλ΅ 2xx, node λ―Έλ±λ‘ μν
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν. μ
λ ν°λ IV7_S03 κ³Ό κ°μ monitoring.ts agentAction(κ²μ¦λ¨)
// 2026-10-16 μμ (rf-obs2): node κ° Running μ΄ μλλ©΄ BLOCKED skip μΆκ° β Suspended node μμ Uninstall νλ©΄ VM μ μ μ€ν¨λ‘ FAILED κ° λ¨λλ€(mc9 g9-1 μ¬λ‘).
// 2026-10-16 μμ λμ ν보 mc1/g1-2(λ μμ΄μ νΈ SERVICE_INACTIVE=μ€μΉλ¨, Suspended)λ Resume μ΄ νμν΄ λ―Έμ€ν
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs } from '../../lib/iv7-obs';
import { agentAction, cellText, rowStatus } from './monitoring';
test('IV7_S09 TC-OBS-009 Log Agent μ κ±° (Uninstall)', async ({ page, dialogs }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const node = process.env.OBS_WRITE_NODE ?? '';
test.skip(!node, 'OBS_WRITE_NODE λ―Έμ§μ (κ³΅μ© node 보νΈ)');
await loginAsAdmin(page);
const f = page.frameLocator('iframe').first();
await openObs(page, 'config');
const st = await rowStatus(f, node);
test.info().annotations.push({ type: 'node-status', description: `${node}:${st || 'ν μμ'} / Monitoring: ${await cellText(f, node, 'Monitoring')} / Log: ${await cellText(f, node, 'Log')}` });
test.skip(st !== 'Running', `BLOCKED: ${node} κ° Running μλ(${st || 'ν μμ'}) β μμ΄μ νΈ μ κ±°λ VM μ μ νμ('Cannot connect to the VM β it must be running')`);
await agentAction(page, f, dialogs, node, 'Log', 'Uninstall');
expect(await cellText(f, node, 'Log')).toMatch(/NOT[_ ]INSTALLED|Install/i);
});tests/iv7/IV7_S10.spec.ts Β· 46μ€ Β· ν΄μ 98f7c330 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S10: TC-OBS-010 Trigger Policy μμ±/λͺ©λ‘/μμ [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-TRIG-01
// νλ©΄: Monitorings > Alert β Policies β + New Policy
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. mc-o11y-manager / Grafana(μλ¦Ό) κΈ°λ
// μ μ°¨: 1.Policies ν 2.λͺ©λ‘(GET /api/o11y/trigger/policy) 3.+ New Policy: title/description/resourceType CPU/aggregation AVG/holdDuration 5m/repeatInterval 1h/threshold infoΒ·warningΒ·critical β μμ±(POST) 4.λͺ©λ‘ λ°μ 5.μμ (202) β λͺ©λ‘μμ μ κ±°
// κΈ°λκ²°κ³Ό: μμ± 201(id), λͺ©λ‘(content[], totalElements), μμ 202
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν.
// 2026-10-16 μμ (첫 μ€ν, rf-obs2): 2026-10-14 μΆμ μ
λ ν°(getByLabel)κ° νλ©΄κ³Ό λ¬λΌ κ΅μ²΄ β νΌ λΌλ²¨μ΄ <label> μ΄ μλ <div> λΌμ
// lib/iv7-obs.ts policyField/createPolicy/deletePolicy μ¬μ©. μμ νμΈμ JS confirm('Delete this policy?')μ΄λΌ fixtures dialogs κ° μλ½. μ€ν¨ν΄λ finally μμ μ μ±
μμ (μ 리)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, createPolicy, deletePolicy, policyRow, clean } from '../../lib/iv7-obs';
export const POLICY = 'iv7-s10-cpu-policy';
test('IV7_S10 TC-OBS-010 Trigger Policy μμ±/λͺ©λ‘/μμ ', async ({ page }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const list = collectResponses(page, /\/api\/o11y\/trigger\/policy\?/);
await loginAsAdmin(page);
const f = await openObs(page, 'alerts');
try {
await test.step('1~2. Policies λͺ©λ‘', async () => {
await expect.poll(() => list.length, { timeout: 30_000 }).toBeGreaterThan(0);
const d = list.at(-1)!.json?.data;
test.info().annotations.push({ type: 'list-before', description: `${list.at(-1)!.status} totalElements=${d?.totalElements} content=${(d?.content ?? []).map((p: any) => p.title).join(',')}` });
expect(Array.isArray(d?.content)).toBe(true);
});
const c = await test.step('3. + New Policy β Create', async () =>
createPolicy(page, f, { title: POLICY, description: 'IV7_S10 test policy', resource: 'CPU', aggregation: 'AVG', hold: '5m', repeat: '1h', info: '50', warning: '70', critical: '90' }));
expect(c.status, 'μμ± 201').toBe(201);
expect(c.id, 'μμ± id').toBeTruthy();
await test.step('4. λͺ©λ‘ λ°μ', async () => {
const row = clean(await policyRow(f, POLICY).innerText());
test.info().annotations.push({ type: 'row', description: row });
expect(row).toMatch(/cpu/i);
expect(row).toContain('I:50 W:70 C:90');
const d = list.at(-1)!.json?.data;
expect((d?.content ?? []).some((p: any) => p.title === POLICY && p.holdDuration === '5m' && p.repeatInterval === '1h' && /avg/i.test(p.aggregationType))).toBe(true);
});
await test.step('5. μμ 202 β λͺ©λ‘μμ μ κ±°', async () => {
expect(await deletePolicy(page, f, POLICY)).toBe(202);
});
} finally {
await deletePolicy(page, f, POLICY).catch(() => 0); // μ 리(μ΄λ―Έ μ§μ μΌλ©΄ μ무κ²λ μ ν¨)
}
});tests/iv7/IV7_S11.spec.ts Β· 56μ€ Β· ν΄μ a4126fd6 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S11: TC-OBS-011 μ μ±
β Node/Infra μ°κ²° [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-TRIG-02
// νλ©΄: Monitorings > Alert β μ μ±
ν Manage β Alarm targets
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. μ μ±
μ‘΄μ¬(IV7_S10 μ μ°¨λ‘ μμ±), λμ node μ‘΄μ¬
// μ μ°¨: 1.Manage β Alarm targets 2.Infra μ ν β λ 벨 Node(λ¨μΌ VM) 3.Add targets(POST /api/o11y/trigger/policy/{id}/node) 4.μ μ±
vms λͺ©λ‘ κ°±μ 5.(μ 리) λμ μ κ±° Β· μ μ±
μμ
// κΈ°λκ²°κ³Ό: 202 ACCEPTED, μ μ±
vms λͺ©λ‘ κ°±μ
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν.
// 2026-10-16 μμ (첫 μ€ν, rf-obs2): μΆμ μ
λ ν° κ΅μ²΄ β μ μ±
μ΄ μμΌλ©΄ Manage κ° μμΌλ―λ‘ μ΄ spec μ΄ μ μ±
μ λ§λ€κ³ (IV7_S10 μ μ°¨) λμ μ§μ΄λ€.
// Alarm targets ν¨λμ 'Infra / Cluster' select + node 체ν¬λ°μ€(label 'g1-2 (g1-2)') + 'Add targets'. λ 벨(Infra/Node) μ ν UI λ μκ³ node 체ν¬κ° λ¨μΌ VM μ°κ²°.
// λμ node λ OBS_TARGET_NODE(κΈ°λ³Έ OBS_NODE) β μ°κ²°λ§ νκ³ node μνλ λ°κΎΈμ§ μμ. μλ¦Ό μ±λμ λΉμ λμ΄ λ°μ‘ μμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, createPolicy, deletePolicy, policyRow, clean, OBS_INFRA, OBS_NODE } from '../../lib/iv7-obs';
const POLICY = 'iv7-s11-target-policy';
const NODE = process.env.OBS_TARGET_NODE ?? OBS_NODE;
test('IV7_S11 TC-OBS-011 μ μ±
β Node/Infra μ°κ²°', async ({ page }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const api = collectResponses(page, /\/api\/o11y\/trigger\/policy\/\d+\/node/);
const list = collectResponses(page, /\/api\/o11y\/trigger\/policy\?/);
await loginAsAdmin(page);
const f = await openObs(page, 'alerts');
try {
await test.step('0. μ¬μ : μ μ±
μμ±(IV7_S10 μ μ°¨)', async () => { await createPolicy(page, f, { title: POLICY, description: 'IV7_S11 target test' }); });
await test.step('1~3. Manage β Alarm targets β InfraΒ·Node β Add targets', async () => {
await policyRow(f, POLICY).getByRole('button', { name: 'Manage', exact: true }).click();
await expect(f.getByRole('button', { name: 'Add targets' })).toBeVisible();
const sel = f.locator('select').filter({ has: f.locator('option', { hasText: new RegExp(`^${OBS_INFRA}$`) }) }).first();
await sel.selectOption({ label: OBS_INFRA });
await page.waitForTimeout(2000);
await f.locator('label').filter({ hasText: new RegExp(`^\\s*${NODE} \\(`) }).first().locator('input[type="checkbox"]').check()
.catch(async () => f.getByLabel(new RegExp(`^${NODE} \\(`)).first().check());
await f.getByRole('button', { name: 'Add targets' }).click();
await expect.poll(() => api.find((a) => a.method === 'POST')?.status ?? 0, { message: 'Add targets POST', timeout: 30_000 }).toBeGreaterThan(0);
const p = api.find((a) => a.method === 'POST')!;
test.info().annotations.push({ type: 'add-targets', description: `${p.status} POST ${p.url} ${JSON.stringify(p.json ?? '').slice(0, 300)}` });
expect(p.status, '202 ACCEPTED').toBe(202);
});
await test.step('4. μ μ±
vms λͺ©λ‘ κ°±μ ', async () => {
await expect.poll(() => (list.at(-1)?.json?.data?.content ?? []).find((x: any) => x.title === POLICY)?.vms?.length ?? 0, { timeout: 30_000 }).toBeGreaterThan(0);
const vms = (list.at(-1)!.json.data.content as any[]).find((x) => x.title === POLICY).vms;
test.info().annotations.push({ type: 'vms', description: JSON.stringify(vms).slice(0, 500) });
expect(JSON.stringify(vms)).toContain(NODE);
const row = clean(await policyRow(f, POLICY).innerText());
test.info().annotations.push({ type: 'row', description: row });
test.info().annotations.push({ type: 'panel', description: clean(await f.locator('body').innerText()).slice(0, 1200) });
});
} finally {
await test.step('5. (μ 리) μ μ±
μμ β μ°κ²°λ λμλ ν¨κ» μ κ±°', async () => {
await page.keyboard.press('Escape').catch(() => {});
const st = await deletePolicy(page, f, POLICY).catch((e) => { test.info().annotations.push({ type: 'cleanup-error', description: String(e).slice(0, 200) }); return -1; });
test.info().annotations.push({ type: 'cleanup', description: `μ μ±
μμ ${st}` });
});
}
});tests/iv7/IV7_S12.spec.ts Β· 67μ€ Β· ν΄μ aa108960 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S12: TC-OBS-012 μλ¦Ό μ±λ μ€μ [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-TRIG-03
// νλ©΄: Monitorings > Alert β μ μ±
Manage β Notification channels
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. μ μ±
μ‘΄μ¬, Grafana contact point(SMTP λ±)Β·μ±λλ³ μ격 ꡬμ±
// μ μ°¨: 1.Notification channels 2.μ§μ μ±λ(GET /api/o11y/trigger/noti/channel) 3.Email μ²΄ν¬ Β· μμ μ μ
λ ₯ 4.Test(POST /api/o11y/trigger/noti/test) 5.Save channels(PUT /api/o11y/trigger/policy/{id}/channel)
// κΈ°λκ²°κ³Ό: 202, notiChannels κ°±μ , (ν
μ€νΈ μ) λ°μ‘ κ²°κ³Ό
// λΉκ³ : μ±λ μ격(SMTPΒ·Slack ν ν° λ±)μ΄ μμΌλ©΄ SKIP(μ§μΉ¨ 4-0). μμ μλ OBS_NOTI_EMAIL
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν.
// 2026-10-16 μμ (첫 μ€ν, rf-obs2): μΆμ μ
λ ν° κ΅μ²΄ β μ΄ spec μ΄ μ μ±
μ λ§λ€κ³ (IV7_S10 μ μ°¨, λμ node μμ) Manage β Notification channels β Email 체ν¬Β·μμ μ β Save channels ν μ μ±
μμ .
// μμ μ κΈ°λ³Έκ°μ λ°μ‘λμ§ μλ μμ½ λλ©μΈ [email protected](OBS_NOTI_EMAIL λ‘ λ³κ²½). 'Test' λ μ€μ λ©μμ§λ₯Ό 보λ΄λ―λ‘('Test delivers a real message through RabbitMQ')
// μμ μΒ·λ°μ‘ μΉμΈμ΄ μμ λλ§(OBS_NOTI_TEST=1) λλ₯Έλ€. μ μ±
μ λμ node κ° μμ΄ μ μ₯λ§μΌλ‘λ μλ¦Όμ΄ λ°μ‘λμ§ μλλ€
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, createPolicy, deletePolicy, policyRow, clean } from '../../lib/iv7-obs';
const POLICY = 'iv7-s12-channel-policy';
test('IV7_S12 TC-OBS-012 μλ¦Ό μ±λ μ€μ ', async ({ page }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const to = process.env.OBS_NOTI_EMAIL || '[email protected]';
const api = collectResponses(page, /\/api\/o11y\/trigger\/(noti|policy\/\d+\/channel)/);
const list = collectResponses(page, /\/api\/o11y\/trigger\/policy\?/);
await loginAsAdmin(page);
const f = await openObs(page, 'alerts');
try {
await test.step('2. μ§μ μ±λ λͺ©λ‘', async () => {
await expect.poll(() => api.filter((a) => /noti\/channel/.test(a.url)).length, { timeout: 30_000 }).toBeGreaterThan(0);
const ch = api.filter((a) => /noti\/channel/.test(a.url)).at(-1)!.json?.data?.notiChannels ?? [];
test.info().annotations.push({ type: 'channels', description: ch.map((c: any) => `${c.id}:${c.name}(${c.type}/${c.provider}, active=${c.isActive})`).join(', ') });
expect(ch.some((c: any) => c.type === 'email' && c.isActive), 'email μ±λ νμ±').toBe(true);
});
await test.step('0. μ¬μ : μ μ±
μμ±(IV7_S10 μ μ°¨, λμ μμ)', async () => { await createPolicy(page, f, { title: POLICY, description: 'IV7_S12 channel test' }); });
await test.step('1Β·3. Manage β Notification channels β Email μ²΄ν¬ Β· μμ μ', async () => {
await policyRow(f, POLICY).getByRole('button', { name: 'Manage', exact: true }).click();
await expect(f.getByRole('button', { name: 'Save channels' })).toBeVisible();
const email = f.locator('label').filter({ hasText: /^\s*Email\s*$/ }).first();
const cb = email.locator('input[type="checkbox"]');
if (await cb.count()) await cb.check(); else await email.click();
await f.getByPlaceholder(/Email addresses/).first().fill(to);
test.info().annotations.push({ type: 'recipient', description: to });
});
await test.step('4. Test λ°μ‘(OBS_NOTI_TEST=1 μΌ λλ§)', async () => {
if (process.env.OBS_NOTI_TEST !== '1') { test.info().annotations.push({ type: 'test-send', description: 'λ―Έμ€ν β μ€μ λ©μμ§ λ°μ‘(μμ μΒ·λ°μ‘ μΉμΈ νμ)' }); return; }
const row = f.locator('div').filter({ has: f.getByPlaceholder(/Email addresses/) }).filter({ has: f.getByRole('button', { name: 'Test', exact: true }) }).last();
await row.getByRole('button', { name: 'Test', exact: true }).click();
await expect.poll(() => api.some((a) => /noti\/test/.test(a.url)), { timeout: 60_000 }).toBe(true);
const t = api.find((a) => /noti\/test/.test(a.url))!;
test.info().annotations.push({ type: 'test-send', description: `${t.status} ${t.method} ${t.url} ${JSON.stringify(t.json ?? '').slice(0, 300)}` });
});
await test.step('5. Save channels β 202, notiChannels κ°±μ ', async () => {
await f.getByRole('button', { name: 'Save channels' }).click();
await expect.poll(() => api.find((a) => a.method === 'PUT')?.status ?? 0, { message: 'Save channels PUT', timeout: 30_000 }).toBeGreaterThan(0);
const p = api.find((a) => a.method === 'PUT')!;
test.info().annotations.push({ type: 'save-channels', description: `${p.status} PUT ${p.url} ${JSON.stringify(p.json ?? '').slice(0, 300)}` });
expect(p.status, 'Save channels 202').toBe(202);
await expect.poll(() => JSON.stringify((list.at(-1)?.json?.data?.content ?? []).find((x: any) => x.title === POLICY)?.notiChannels ?? []), { timeout: 30_000 }).toMatch(/email/i);
const nc = (list.at(-1)!.json.data.content as any[]).find((x) => x.title === POLICY).notiChannels;
test.info().annotations.push({ type: 'notiChannels', description: JSON.stringify(nc).slice(0, 500) });
test.info().annotations.push({ type: 'row', description: clean(await policyRow(f, POLICY).innerText()) });
});
} finally {
await test.step('(μ 리) μ μ±
μμ ', async () => {
const st = await deletePolicy(page, f, POLICY).catch((e) => { test.info().annotations.push({ type: 'cleanup-error', description: String(e).slice(0, 200) }); return -1; });
test.info().annotations.push({ type: 'cleanup', description: `μ μ±
μμ ${st}` });
});
}
});tests/iv7/IV7_S13.spec.ts Β· 31μ€ Β· ν΄μ f0125ce5 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S13: TC-OBS-013 Trigger / Notification History [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-TRIG-04 β κΈ°μ‘΄ spec(mcmp-e2e deploy/scenarios/shared/step-handlers/obs/trigger.ts runObsTrig04)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Alert (iframe /embed/alerts/{ns}) β Policies(νΈλ¦¬κ±° μ΄λ ₯) Β· Notification History ν
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. TC-OBS-011Β·012 μλ£, μκ³μΉ μ΄κ³Ό νΈλ¦¬κ±° λ°μ
// μ μ°¨: 1.νΈλ¦¬κ±° λ°μ ν History νμΈ 2.νΈλ¦¬κ±° μ΄λ ₯(GET /api/o11y/trigger/history) severity/value/threshold 3.(μ΅μ
) μ½λ©νΈ μμ 4.μλ¦Ό λ°μ‘ μ΄λ ₯(GET /api/o11y/trigger/noti/history) 5.Notification History ν λ°μ‘ λ‘κ·Έ
// κΈ°λκ²°κ³Ό: νΈλ¦¬κ±° μ΄λ ₯ content[](severity, value, threshold), μλ¦Ό μ΄λ ₯ content[](channelName, isSucceeded)
// λΉκ³ : μ½κΈ° TC λ‘ λ¨Όμ μ§ν(μ¬μ©μ κ²°μ 2026-10-14). μ μ±
Β·νΈλ¦¬κ±°κ° μμΌλ©΄ SKIP(μ ν TC-OBS-010~012 λ―Έμ€ν)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, clean } from '../../lib/iv7-obs';
test('IV7_S13 TC-OBS-013 Trigger / Notification History', async ({ page }) => {
const policy = collectResponses(page, /\/api\/o11y\/trigger\/policy(\?|$)/);
const notiHist = collectResponses(page, /\/api\/o11y\/trigger\/noti\/history/);
const trigHist = collectResponses(page, /\/api\/o11y\/trigger\/history/);
await loginAsAdmin(page);
const f = await openObs(page, 'alerts');
await expect.poll(() => policy.length, { timeout: 30_000 }).toBeGreaterThan(0);
const policies = policy.at(-1)!.json?.data?.totalElements ?? 0;
await test.step('4~5. Notification History ν', async () => {
await f.getByRole('button', { name: 'Notification History', exact: true }).click();
await page.waitForTimeout(4000);
test.info().annotations.push({ type: 'noti-history', description: `${notiHist.map((n) => `${n.status} ${n.url.slice(0, 120)} total=${n.json?.data?.totalElements}`).join(' | ') || '(νΈμΆ μμ)'} / ${clean(await f.locator('body').innerText()).slice(0, 300)}` });
});
const trig = trigHist.at(-1)?.json?.data?.totalElements ?? 0;
const noti = notiHist.at(-1)?.json?.data?.totalElements ?? 0;
test.info().annotations.push({ type: 'counts', description: `μ μ±
${policies} / νΈλ¦¬κ±° μ΄λ ₯ ${trig} / μλ¦Ό μ΄λ ₯ ${noti}` });
test.skip(!policies || (!trig && !noti), 'μ μ±
Β·νΈλ¦¬κ±° μ΄λ ₯ μμ β μ ν TC-OBS-010~012(μ μ±
μμ±Β·λμ μ°κ²°Β·μ±λ) λ―Έμ€ν');
expect(noti + trig).toBeGreaterThan(0);
});tests/iv7/IV7_S14.spec.ts Β· 30μ€ Β· ν΄μ c1c4f52b Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S14: TC-OBS-014 NS λͺ©λ‘ μ‘°ν [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-NS-01 β κΈ°μ‘΄ spec μμ(λ κ±°μλ ꡬ UI DOM μ‘΄μ¬λ§ νμΈ). 2026-10-14 μ κ· μμ±
// νλ©΄: Monitorings > Monitoring (iframe :18081/embed/monitoring/{ns})
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. mc-o11y-manager Β· CB-Tumblebug κΈ°λ, NS 1κ° μ΄μ
// μ μ°¨: 1.Monitoring μ§μ
2.νλ©΄μ΄ λΆλ₯΄λ NS λͺ©λ‘(GET /tumblebug/ns) 3.μ½μ Project(=NS) λͺ©λ‘μ΄ NS λͺ©λ‘μ ν¬ν¨ 4.iframe μλ¨ 'Namespace β {μ ν NS}'
// κΈ°λκ²°κ³Ό: ns λ°°μ΄(id, name), NS λͺ©λ‘ νμ
// λΉκ³ : μ½μ μλ² λ νλ©΄μλ NS μ
λ ν°κ° μκ³ μ½μ μλ¨ Project μ νμ΄ NS λ‘ μ λ¬λλ€(2026-10-14) β Project μ νμΉΈ = NS λͺ©λ‘μΌλ‘ νμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, OBS_NS, clean } from '../../lib/iv7-obs';
test('IV7_S14 TC-OBS-014 NS λͺ©λ‘ μ‘°ν', async ({ page }) => {
const res = collectResponses(page, /\/tumblebug\/ns(\?|$)/);
await loginAsAdmin(page);
const f = await openObs(page, 'monitoring');
await test.step('1~2. NS λͺ©λ‘ μλ΅(GET /tumblebug/ns)', async () => {
await expect.poll(() => res.length, { timeout: 30_000 }).toBeGreaterThan(0);
const ns: any[] = res.at(-1)!.json?.ns ?? [];
test.info().annotations.push({ type: 'ns', description: ns.map((n) => `${n.id}(${n.name})`).join(', ') });
expect(ns.length).toBeGreaterThan(0);
expect(ns.every((n) => n.id && n.name)).toBe(true);
const projects = (await page.locator('#select-current-project option').allInnerTexts()).map(clean).filter((p) => p && !/select/i.test(p));
test.info().annotations.push({ type: 'console-projects', description: projects.join(', ') });
for (const p of projects) expect(ns.map((n) => n.id), `Project ${p} κ° NS λͺ©λ‘μ`).toContain(p);
});
await test.step('3. iframe μλ¨ Namespace νμ', async () => {
await expect(f.getByText(new RegExp(`Namespace\\s*β\\s*${OBS_NS}`)).first()).toBeVisible();
});
});tests/iv7/IV7_S15.spec.ts Β· 34μ€ Β· ν΄μ db2d0c54 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S15: TC-OBS-015 NS μ ν β Infra λͺ©λ‘ [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-NS-02 β 2026-10-14 μ κ· μμ±(λ κ±°μλ λλ‘λ€μ΄ DOM μ‘΄μ¬λ§ νμΈ)
// νλ©΄: Monitorings > Monitoring (Node ν) / Logs μ Workload(Infra/Cluster) μ
λ ν°
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ NS μ Infra 1κ° μ΄μ
// μ μ°¨: 1.Project(=NS) default μ ν 2.Infra λͺ©λ‘(GET /tumblebug/ns/{ns}/infra) 3.Monitoring νλ©΄μ Infra λͺ©λ‘Β·'N Nodes / M Infras' 4.Logs Workload μ
λ ν° λͺ©λ‘
// κΈ°λκ²°κ³Ό: infra λ°°μ΄(id, name, status, node[]), Infra μ
λ ν° νμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, OBS_NS, clean } from '../../lib/iv7-obs';
test('IV7_S15 TC-OBS-015 NS μ ν β Infra λͺ©λ‘', async ({ page }) => {
const res = collectResponses(page, new RegExp(`/tumblebug/ns/${OBS_NS}/infra(\\?|$)`));
await loginAsAdmin(page);
const f = await openObs(page, 'monitoring');
let ids: string[] = [];
await test.step('1~3. Infra λͺ©λ‘ μλ΅ = Monitoring νλ©΄', async () => {
await expect.poll(() => res.length, { timeout: 30_000 }).toBeGreaterThan(0);
const infra: any[] = res.at(-1)!.json?.infra ?? [];
ids = infra.map((i) => i.id);
const body = clean(await f.locator('body').innerText());
test.info().annotations.push({ type: 'infra', description: infra.map((i) => `${i.id}(${i.status}, node ${(i.node ?? []).length})`).join(', ') });
test.info().annotations.push({ type: 'summary', description: body.match(/\d+ Nodes \/ \d+ Infras/)?.[0] ?? '' });
expect(ids.length).toBeGreaterThan(0);
expect(Number(body.match(/(\d+) Infras/)?.[1])).toBe(ids.length);
for (const id of ids) expect(body, `Infra ${id} νμ`).toContain(id);
});
await test.step('4. Logs μ Workload(Infra/Cluster) μ
λ ν°', async () => {
const g = await openObs(page, 'logs');
const opts = (await g.locator('select').first().locator('option').allInnerTexts()).map(clean).filter((o) => !/^Select/.test(o));
test.info().annotations.push({ type: 'logs-workload', description: opts.join(', ') });
expect(opts.sort()).toEqual([...ids].sort());
});
});tests/iv7/IV7_S16.spec.ts Β· 30μ€ Β· ν΄μ e1851c70 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S16: TC-OBS-016 Infra μ ν β Node λͺ©λ‘ [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-NS-03 β 2026-10-14 μ κ· μμ±(λ κ±°μλ DOM μ‘΄μ¬λ§ νμΈ)
// νλ©΄: Monitorings > Config (iframe /embed/config/{ns}) β Infra λ³ Node ν(Name/Node ID/Status/Monitoring Agent/Log Agent)
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ Infra(OBS_INFRA, κΈ°λ³Έ mc1) μ‘΄μ¬
// μ μ°¨: 1.Config μ§μ
2.Infra μμΈ(GET /tumblebug/ns/{ns}/infra/{infra}) λλ λͺ©λ‘μ node 3.λ§€λμ λ±λ‘ node μν(GET /api/o11y/monitoring/{ns}/{infra}/node) 4.νλ©΄μ node νκ³Ό Agent μνκ° μλ΅κ³Ό μΌμΉ
// κΈ°λκ²°κ³Ό: node λ°°μ΄, λ
Έλλ³ monitoring_agent_status / log_agent_status νμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, OBS_NS, OBS_INFRA, clean } from '../../lib/iv7-obs';
test('IV7_S16 TC-OBS-016 Infra μ ν β Node λͺ©λ‘', async ({ page }) => {
const res = collectResponses(page, new RegExp(`/api/o11y/monitoring/${OBS_NS}/${OBS_INFRA}/node$`));
await loginAsAdmin(page);
const f = await openObs(page, 'config');
await test.step(`1~4. ${OBS_INFRA} node ν Β· Agent μν = μλ΅`, async () => {
await expect.poll(() => res.length, { timeout: 30_000 }).toBeGreaterThan(0);
const nodes: any[] = res.at(-1)!.json?.data ?? [];
test.info().annotations.push({ type: 'nodes', description: nodes.map((n) => `${n.node_id}: mon=${n.monitoring_agent_status} log=${n.log_agent_status}`).join(', ') });
expect(nodes.length).toBeGreaterThan(0);
for (const n of nodes) {
const row = f.locator('tr').filter({ has: f.locator('td', { hasText: new RegExp(`^${n.node_id}$`) }) }).first();
const t = clean(await row.innerText());
test.info().annotations.push({ type: `row-${n.node_id}`, description: t });
expect(t).toContain(n.node_id);
expect(t.toUpperCase()).toContain(String(n.monitoring_agent_status).toUpperCase());
expect(t.toUpperCase()).toContain(String(n.log_agent_status).toUpperCase());
}
});
});tests/iv7/IV7_S17.spec.ts Β· 30μ€ Β· ν΄μ 2f0355ad Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S17: TC-OBS-017 λͺ¨λν°λ§ νλ©΄ μ§μ
[μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-MON-DATA-01 β κΈ°μ‘΄ spec(mcmp-e2e mc-web-console/specs/obs/TC-OBS-MON-DATA-01-monitoring-data-screen.spec.ts)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Monitoring β node λμ보λ(iframe /embed/monitoring/{ns}/{infra}/{node})
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ node(OBS_INFRA/OBS_NODE, κΈ°λ³Έ mc1/g1-1)
// μ μ°¨: 1.Monitoring μ§μ
β node λμ보λ κ²½λ‘ 2.μΈ‘μ νλͺ© λ©ν(GET /api/o11y/monitoring/influxdb/measurement) 3.μ’μΈ‘ 컨νΈλ‘€(Agent/API, Workload/Server/Measurement/Metric/Aggregation/Range/Period, Start Monitoring)κ³Ό μ°¨νΈ μμ
// κΈ°λκ²°κ³Ό: λͺ¨λν°λ§ λμ보λ νλ©΄, μΈ‘μ νλͺ© λͺ©λ‘Β·μ°¨νΈ μ»¨νΈλ‘€
// λΉκ³ : Infra κ°μμ node μΉ΄λ ν΄λ¦μΌλ‘λ λμ보λκ° μ΄λ¦¬μ§ μμ μνΈ κ²½λ‘λ‘ iframe μ΄λ(2026-10-14)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, obsGoto, collectResponses, OBS_NS, OBS_INFRA, OBS_NODE } from '../../lib/iv7-obs';
test('IV7_S17 TC-OBS-017 λͺ¨λν°λ§ νλ©΄ μ§μ
', async ({ page }) => {
const meas = collectResponses(page, /\/api\/o11y\/monitoring\/influxdb\/measurement$/);
await loginAsAdmin(page);
let f = await openObs(page, 'monitoring');
f = await test.step(`1. node λμ보λ /embed/monitoring/${OBS_NS}/${OBS_INFRA}/${OBS_NODE}`, () => obsGoto(page, f, `/embed/monitoring/${OBS_NS}/${OBS_INFRA}/${OBS_NODE}`));
await test.step('2. μΈ‘μ νλͺ© λ©ν μλ΅', async () => {
await expect.poll(() => meas.length, { timeout: 30_000 }).toBeGreaterThan(0);
const m: any[] = meas.at(-1)!.json?.data ?? [];
test.info().annotations.push({ type: 'measurements', description: m.map((x) => x.measurement).join(', ') });
expect(m.map((x) => x.measurement)).toEqual(expect.arrayContaining(['cpu', 'mem', 'disk']));
});
await test.step('3. 컨νΈλ‘€ Β· μ°¨νΈ μμ', async () => {
await expect(f.getByText('Monitoring Trend').first()).toBeVisible();
for (const l of ['Workload', 'Server', 'Measurement', 'Metric', 'Aggregation', 'Range', 'Period']) await expect(f.getByText(l, { exact: true }).first()).toBeVisible();
await expect(f.getByRole('button', { name: 'Start Monitoring' })).toBeVisible();
await expect(f.getByText(`Node: ${OBS_NODE}`).first()).toBeVisible();
});
});tests/iv7/IV7_S18.spec.ts Β· 29μ€ Β· ν΄μ 10c50ab9 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S18: TC-OBS-018 μ½μ iframe λͺ¨λν°λ§ μ§μ
[μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-IFRAME-01 β κΈ°μ‘΄ spec(mcmp-e2e mc-web-console/specs/obs/TC-OBS-IFRAME-01-console-iframe.spec.ts)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: mc-web-console Monitorings λ©λ΄ 6κ°(Monitoring/Logs/Config/Insight/Alert/Tracing) β iframe :18081/embed/...
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. mc-web-console μ°λ μ€μ
// μ μ°¨: 1.κ° λ©λ΄ μ§μ
β iframe src κ° /embed/{monitoring|logs|config|insight|alerts|trace}/{ns} 2.EmbedLayout(ν€λ/μ¬μ΄λλ° μλ νλ©΄)μΌλ‘ λ‘λ 3.νλ©΄λ³ ν΅μ¬ μμ λμ
// κΈ°λκ²°κ³Ό: λ€λΉκ²μ΄μ
ν¬λ‘¬ μλ μλ² λ νλ©΄, iframe λ΄ κΈ°λ₯ μ μ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, OBS_NS, clean, type ObsMenu } from '../../lib/iv7-obs';
const EXPECT: Record<ObsMenu, RegExp> = {
monitoring: /Namespace\s*β/, logs: /Log Manage/, config: /Monitor Setting/, insight: /Anomaly Detection/, alerts: /Trigger Policies|Policies/, trace: /Trace Manage/,
};
test('IV7_S18 TC-OBS-018 μ½μ iframe λͺ¨λν°λ§ μ§μ
', async ({ page }) => {
await loginAsAdmin(page);
for (const menu of Object.keys(EXPECT) as ObsMenu[]) {
await test.step(`${menu}: /embed/${menu}/${OBS_NS} Β· μλ² λ λ μ΄μμ Β· ν΅μ¬ μμ`, async () => {
const f = await openObs(page, menu);
const path = new URL(f.url()).pathname;
const body = clean(await f.locator('body').innerText());
const chrome = await f.locator('nav, aside, header .navbar, .sidebar').count();
test.info().annotations.push({ type: menu, description: `${path} / navΒ·sidebar μμ ${chrome} / ${body.slice(0, 120)}` });
expect(path).toBe(`/embed/${menu}/${OBS_NS}`);
expect(body).toMatch(EXPECT[menu]);
expect(body, 'λ
립 μ€ν νλ©΄μ μλ¨ λ€λΉ λ©λ΄ μμ').not.toMatch(/MC-Observability\s+(Monitoring\s+Logs|Dashboard)/);
});
}
});tests/iv7/IV7_S19.spec.ts Β· 37μ€ Β· ν΄μ 8df7af22 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S19: TC-OBS-019 Agent κΈ°λ° μκ³μ΄ λ©νΈλ¦ (InfluxDB) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-METRIC-01 β κΈ°μ‘΄ spec(mcmp-e2e mc-web-console/specs/obs/TC-OBS-METRIC-01-agent-timeseries.spec.ts)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Monitoring β node λμ보λ(/embed/monitoring/{ns}/{infra}/{node}, λ°μ΄ν°μμ€ Agent)
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ node μ Monitoring Agent SUCCESS Β· μμ§ λ°μ΄ν° μ‘΄μ¬
// μ μ°¨: 1.node λμ보λ(Agent) 2.Measurement cpu Β· Metric usage_idle Β· Aggregation Mean Β· Range 1H Β· Period 1m 3.Start Monitoring 4.λ
Έλ λ©νΈλ¦(POST /api/o11y/monitoring/influxdb/metric/{ns}/{infra}/{node}) 5.μκ³μ΄ μ°¨νΈ
// κΈ°λκ²°κ³Ό: MetricDTO[](name, columns, tags, values[[time,value]]), μκ³μ΄ μ°¨νΈ νμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, obsGoto, collectResponses, selectNear, OBS_NS, OBS_INFRA, OBS_NODE } from '../../lib/iv7-obs';
test('IV7_S19 TC-OBS-019 Agent κΈ°λ° μκ³μ΄ λ©νΈλ¦ (InfluxDB)', async ({ page }) => {
const metric = collectResponses(page, new RegExp(`/influxdb/metric/${OBS_NS}/${OBS_INFRA}/${OBS_NODE}$`));
await loginAsAdmin(page);
let f = await openObs(page, 'monitoring');
f = await obsGoto(page, f, `/embed/monitoring/${OBS_NS}/${OBS_INFRA}/${OBS_NODE}`);
await test.step('2. cpu Β· usage_idle Β· Mean Β· 1H Β· 1m', async () => {
await selectNear(f, 'Measurement', 'cpu');
await page.waitForTimeout(1500);
await selectNear(f, 'Metric', 'usage_idle');
await selectNear(f, 'Aggregation', 'Mean');
await selectNear(f, 'Range', '1H');
await selectNear(f, 'Period', '1m');
});
await test.step('3~5. Start Monitoring β λ©νΈλ¦ μλ΅ Β· μ°¨νΈ', async () => {
const n0 = metric.length;
await f.getByRole('button', { name: 'Start Monitoring' }).click();
await expect.poll(() => metric.slice(n0).find((m) => m.json?.data?.[0]?.columns?.includes('usage_idle')), { timeout: 30_000 }).toBeTruthy();
const d = metric.slice(n0).find((m) => m.json?.data?.[0]?.columns?.includes('usage_idle'))!.json.data[0];
const vals = (d.values ?? []).filter((v: any[]) => v[1] !== null);
test.info().annotations.push({ type: 'metric', description: `${d.name} ${JSON.stringify(d.tags)} columns=${d.columns} κ° ${vals.length}κ° μ: ${JSON.stringify(vals.slice(0, 2))}` });
expect(vals.length, 'μμ§λ μκ³μ΄ κ°').toBeGreaterThan(0);
await expect(f.locator('svg:visible, canvas:visible').first()).toBeVisible();
await expect(f.getByText(/Download CSV/).first(), 'μ°¨νΈ μμ(λ€μ΄λ‘λ λ©λ΄)').toBeVisible();
test.info().annotations.push({ type: 'chart', description: (await f.locator('body').innerText()).replace(/\s+/g, ' ').match(/Node: [^\n]{0,200}/)?.[0] ?? '' });
});
});tests/iv7/IV7_S20.spec.ts Β· 33μ€ Β· ν΄μ 9e224647 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S20: TC-OBS-020 CSP API λ©νΈλ¦ (cb-spider) [μ μ, AWS]
// v0.6 TC-ID: TC-OBS-METRIC-02 β κΈ°μ‘΄ spec(mcmp-e2e mc-web-console/specs/obs/TC-OBS-METRIC-02-csp-api-metrics.spec.ts)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Monitoring β node λμ보λ β 'API' λ°μ΄ν°μμ€(?source=csp)
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ node Running Β· CSP Credential μ ν¨(Agent λΆνμ)
// μ μ°¨: 1.node λμ보λμμ API(CSP) μ ν 2.connectionNameΒ·cspResourceName 3.API Metric CPU Usage Percent 4.CSP λ©νΈλ¦(GET /api/o11y/monitoring/csp/node/{cspResourceName}/{metricType}?ConnectionName=β¦) 5.CSP μκ³μ΄ μ°¨νΈ
// κΈ°λκ²°κ³Ό: SpiderMonitoringInfo.Data(metricName, metricUnit, timestampValues[]), CSP λ©νΈλ¦ μ°¨νΈ
// λΉκ³ : AWS λ memory_usage λ―Έμ§μ(unsupported:true, 2026-10-14) β νμ μ CPU λ‘
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, obsGoto, collectResponses, OBS_NS, OBS_INFRA, OBS_NODE } from '../../lib/iv7-obs';
test('IV7_S20_AWS TC-OBS-020 CSP API λ©νΈλ¦ (cb-spider)', async ({ page }) => {
const csp = collectResponses(page, /\/api\/o11y\/monitoring\/csp\/node\//);
await loginAsAdmin(page);
let f = await openObs(page, 'monitoring');
f = await obsGoto(page, f, `/embed/monitoring/${OBS_NS}/${OBS_INFRA}/${OBS_NODE}`);
await test.step('1. λ°μ΄ν°μμ€ API(CSP) μ ν', async () => {
await f.getByRole('button', { name: 'API', exact: true }).click();
await page.waitForTimeout(4000);
await expect(f.getByText('API Metric').first()).toBeVisible();
});
await test.step('3~5. CPU Usage Percent β CSP λ©νΈλ¦ μλ΅ Β· μ°¨νΈ', async () => {
await f.getByRole('button', { name: 'CPU Usage Percent' }).click();
await expect.poll(() => csp.find((c) => /cpu_usage/.test(c.url)), { timeout: 30_000 }).toBeTruthy();
const r = csp.filter((c) => /cpu_usage/.test(c.url)).at(-1)!;
const unsupported = csp.filter((c) => c.json?.unsupported).map((c) => c.url.match(/node\/[^/]+\/(\w+)/)?.[1]);
test.info().annotations.push({ type: 'csp', description: `${r.url} β ${r.json?.metricName} (${r.json?.metricUnit}) κ° ${(r.json?.timestampValues ?? []).length}κ° / unsupported: ${unsupported.join(', ') || 'μμ'}` });
expect(r.url).toMatch(/ConnectionName=[\w-]+/);
expect((r.json?.timestampValues ?? []).length).toBeGreaterThan(0);
await expect(f.getByText(/CPU Usage Percent \(Percent\)/).first()).toBeVisible();
});
});tests/iv7/IV7_S21.spec.ts Β· 44μ€ Β· ν΄μ a51ea5a9 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S21: TC-OBS-021 Infra / NS λ 벨 μ€λ²λ·° [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-METRIC-04 β κΈ°μ‘΄ spec(mcmp-e2e mc-web-console/specs/obs/TC-OBS-METRIC-04-infra-ns-overview.spec.ts)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Monitoring (NS μ€λ²λ·° /embed/monitoring/{ns}, Infra μ€λ²λ·° /embed/monitoring/{ns}/{infra})
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. μμ§ λ°μ΄ν° μ‘΄μ¬(TC-OBS-019 λλ 020)
// μ μ°¨: 1.NS μ€λ²λ·° 2.λ
Έλλ³ λ©νΈλ¦ λ³λ ¬ μ‘°ν(agent: influxdb/metric) 3.Node ν λ
Έλ μΉ΄λ(CPU/Memory/Disk) 4.K8s ν 5.λ°μ΄ν°μμ€ Agent/API ν κΈ β csp/node μ‘°ν
// κΈ°λκ²°κ³Ό: λ
Έλ/ν΄λ¬μ€ν° λ©νΈλ¦ μμ½ μΉ΄λ(CPU/Memory/Disk)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, obsGoto, collectResponses, OBS_NS, OBS_INFRA, clean } from '../../lib/iv7-obs';
test('IV7_S21 TC-OBS-021 Infra / NS λ 벨 μ€λ²λ·°', async ({ page }) => {
const agent = collectResponses(page, /\/influxdb\/metric\//);
const csp = collectResponses(page, /\/api\/o11y\/monitoring\/csp\/node\//);
await loginAsAdmin(page);
let f = await openObs(page, 'monitoring');
await test.step('1~3. NS μ€λ²λ·° Β· λ
Έλ μΉ΄λ CPU/Memory/Disk', async () => {
await expect.poll(() => agent.length, { timeout: 30_000 }).toBeGreaterThan(0);
const body = clean(await f.locator('body').innerText());
test.info().annotations.push({ type: 'ns-overview', description: `agent μ‘°ν ${agent.length}건 / ${body.slice(0, 300)}` });
// 2026-10-14 κ΄μ°°: mc1/g1-1 μΉ΄λκ° 'No data' β νλ©΄μ mc1/g1-1 λμ κ°μ node μ΄λ¦μ mt1/g1-1 μ μ‘°ν(μ΄λ¦ μΆ©λ μμ¬). κΈ°λ‘λ§ νλ€
const asked = [...new Set(agent.map((a) => a.url.replace(/.*influxdb\/metric\//, '')))];
test.info().annotations.push({ type: 'requested-nodes', description: asked.join(', ') });
for (const k of ['CPU Used', 'Memory Used', 'Disk Used']) expect(body).toContain(k);
});
await test.step(`Infra μ€λ²λ·° /embed/monitoring/${OBS_NS}/${OBS_INFRA}: κ° μλ μΉ΄λ`, async () => {
f = await obsGoto(page, f, `/embed/monitoring/${OBS_NS}/${OBS_INFRA}`);
await expect.poll(async () => /CPU Used [\d.]+%/.test(clean(await f.locator('body').innerText())), { timeout: 60_000 }).toBe(true);
const body = clean(await f.locator('body').innerText());
test.info().annotations.push({ type: 'infra-overview', description: body.slice(0, 500) });
expect(body).toContain(`Infra Overview β ${OBS_INFRA}`);
});
await test.step('4. K8s ν', async () => {
await f.getByRole('button', { name: 'K8s', exact: true }).click();
await page.waitForTimeout(3000);
test.info().annotations.push({ type: 'k8s', description: clean(await f.locator('body').innerText()).match(/\d+ Nodes \/ \d+ Clusters[^.]*/)?.[0] ?? '' });
await f.getByRole('button', { name: 'Node', exact: true }).click();
});
await test.step('5. λ°μ΄ν°μμ€ API ν κΈ β csp/node μ‘°ν', async () => {
await f.getByRole('button', { name: 'API', exact: true }).click();
await expect.poll(() => csp.length, { timeout: 60_000 }).toBeGreaterThan(0);
test.info().annotations.push({ type: 'csp', description: `csp/node μ‘°ν ${csp.length}건: ${csp.slice(0, 3).map((c) => c.url.split('?')[0]).join(', ')}` });
});
});tests/iv7/IV7_S22.spec.ts Β· 33μ€ Β· ν΄μ 40bcbd88 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S22: TC-OBS-022 K8s Cluster λ
Έλ λ©νΈλ¦ [μ μ, AWS]
// v0.6 TC-ID: TC-OBS-METRIC-03 β κΈ°μ‘΄ spec(mcmp-e2e mc-web-console/specs/obs/TC-OBS-METRIC-03-k8s-node-metrics.spec.ts)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Monitoring β K8s ν β ν΄λ¬μ€ν° β NodeGroup β Node λμ보λ(/embed/monitoring/{ns}/k8s/{connection}/{cluster}/{nodeGroup}/{n})
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/{K8s κ° μλ Project}. mc-infra-manager λ‘ K8s ν΄λ¬μ€ν° μμ±, CSP Credential μ ν¨
// μ μ°¨: 1.K8s ν(λ°μ΄ν°μμ€ API) 2.ν΄λ¬μ€ν° λͺ©λ‘(GET /tumblebug/ns/{ns}/k8sCluster, /api/o11y/monitoring/csp/clusters?ConnectionName=β¦) 3.Cluster β NodeGroup β Node 4.λ
Έλ λ©νΈλ¦(GET /api/o11y/monitoring/csp/cluster/{cluster}/{ng}/{n}/{metricType}) 5.μ°¨νΈ
// κΈ°λκ²°κ³Ό: SpiderMonitoringInfo.Data, ν΄λ¬μ€ν° λ
Έλ λ©νΈλ¦ μ°¨νΈ
// λΉκ³ : ν΄λ¬μ€ν°κ° μμΌλ©΄ SKIP(μ ν λ―ΈμΆ©μ‘±). λμ Project λ OBS_K8S_NS(κΈ°λ³Έ OBS_NS)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, OBS_NS, clean } from '../../lib/iv7-obs';
const NS = process.env.OBS_K8S_NS ?? OBS_NS;
test('IV7_S22_AWS TC-OBS-022 K8s Cluster λ
Έλ λ©νΈλ¦', async ({ page }) => {
const clusters = collectResponses(page, /\/k8sCluster$|\/csp\/clusters\?/);
const metrics = collectResponses(page, /\/api\/o11y\/monitoring\/csp\/cluster\//);
await loginAsAdmin(page);
const f = await openObs(page, 'monitoring', NS);
await f.getByRole('button', { name: 'K8s', exact: true }).click();
await page.waitForTimeout(6000);
const body = clean(await f.locator('body').innerText());
const tb = clusters.find((c) => /k8sCluster$/.test(c.url))?.json?.K8sClusterInfo ?? [];
test.info().annotations.push({ type: 'k8s', description: `${body.match(/\d+ Nodes \/ \d+ Clusters[^.]*/)?.[0] ?? ''} / tumblebug ${tb.length}건 / csp ${clusters.filter((c) => /csp\/clusters/.test(c.url)).map((c) => (c.json ?? []).length).join(',')}` });
test.skip(!tb.length, `NS ${NS} μ K8s ν΄λ¬μ€ν° μμ(μ ν λ―ΈμΆ©μ‘±)`);
// λ°μ΄ν°μμ€ κΈ°λ³Έκ°μ Agent(λ
Έλ μμ΄μ νΈ μμ§κ°) β CSP λ
Έλ λ©νΈλ¦μ 'API' ν κΈμμ μ‘°νλλ€(2026-10-15 p1/k1s νμΈ: Agent λ '0/1 node(s) reporting', API λ csp/cluster/{cspId}/{ng}/{n}/{metric} 7μ’
)
await test.step('3~5. λ°μ΄ν°μμ€ API β ν΄λ¬μ€ν° NodeGroup λ
Έλ λ©νΈλ¦', async () => {
await f.getByRole('button', { name: 'API', exact: true }).click();
await expect.poll(() => metrics.length, { timeout: 60_000 }).toBeGreaterThan(0);
const m = metrics.find((x) => (x.json?.timestampValues ?? []).length > 0);
test.info().annotations.push({ type: 'metric', description: metrics.map((x) => `${x.url.split('?')[0]} ${(x.json?.timestampValues ?? []).length}`).join(', ') });
expect(m, 'κ°μ΄ μλ ν΄λ¬μ€ν° λ
Έλ λ©νΈλ¦').toBeTruthy();
});
});tests/iv7/IV7_S23.spec.ts Β· 91μ€ Β· ν΄μ 6c91f225 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S23: TC-OBS-023 Anomaly Detection μ€μ [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-INSIGHT-01
// νλ©΄: Monitorings > Insight β Anomaly Detection β + New Setting
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. μμ§ λ°μ΄ν°(TC-OBS-AGENT-04), insight μμ§ κΈ°λ
// μ μ°¨: 1.Anomaly Detection ν 2.μ΅μ
Β·μΈ‘μ νλͺ©(GET β¦/anomaly-detection/options, /measurement) 3.μΈ‘μ νλͺ© mem Β· λμ node Β· μ£ΌκΈ° 5m β μμ±(POST β¦/settings) 4.μ€μ λͺ©λ‘ λ°μ 5.(μ 리) μμ (DELETE β¦/settings/{seq})
// κΈ°λκ²°κ³Ό: μμ±λ μ€μ (settingSeq), μ€μ λͺ©λ‘ νμ
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν.
// 2026-10-16 μμ (첫 μ€ν, rf-obs2): μΆμ μ
λ ν° κ΅μ²΄ β '+ New Setting' νΌμ select λ <label>(Scope β Infra / Cluster *, Scope β Node *, Measurement *, Execution Interval *) λ€μ select.
// Node select λ Infra μ ν μ disabled. λͺ©λ‘ ν 'default/{infra}/{node} | mem | 5m | Last Run | Delete'. λμ node λ OBS_TARGET_NODE(κΈ°λ³Έ OBS_NODE) β
// IV7_S24(μ΄λ ₯ μ‘°ν)κ° μ°λ κΈ°μ‘΄ μ€μ (mc1/g1-1 cpu)κ³Ό κ²ΉμΉμ§ μκ² λ€λ₯Έ node λ₯Ό μ§μ νλ€. μ€ν¨ν΄λ finally μμ λ§λ μ€μ μμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, clean, OBS_NS, OBS_INFRA, OBS_NODE } from '../../lib/iv7-obs';
const NODE = process.env.OBS_TARGET_NODE ?? OBS_NODE;
test('IV7_S23 TC-OBS-023 Anomaly Detection μ€μ ', async ({ page }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const api = collectResponses(page, /\/anomaly-detection\/(settings|options|measurement)/);
await loginAsAdmin(page);
let f0 = await openObs(page, 'insight');
// iframe μ΄ νλ‘μ νΈ μ ν λ€ λ€μ λ‘λλμ΄ Frame μ΄ detach λλ κ²½μ°κ° μμ΄(2026-10-16 2μ°¨ μ€ν) λ§€λ² νμ¬ insight frame μ μ°Ύλλ€
const cur = () => page.frames().filter((x) => !x.isDetached() && x.url().includes(':18081') && x.url().includes('/embed/insight')).at(-1) ?? f0;
const sel = (label: string) => cur().locator(`xpath=//label[starts-with(normalize-space(),"${label}")]/following::select[1]`).first();
const rowOf = () => cur().locator('tr').filter({ hasText: `${OBS_NS}/${OBS_INFRA}/${NODE}` }).filter({ has: cur().locator('td', { hasText: /^\s*mem\s*$/ }) }).filter({ has: cur().locator('td', { hasText: /^\s*5m\s*$/ }) }); // μ
λ¨μλ‘ λΉκ΅(textContent λ μ
μ¬μ΄ κ³΅λ°±μ΄ μμ΄ \b μ κ·μμ΄ μ λ§μ)
const settingsList = () => (api.filter((a) => a.method === 'GET' && /settings$/.test(a.url)).at(-1)?.json?.data ?? []) as any[];
let created = false;
try {
await test.step('1~2. Anomaly Detection ν Β· μ΅μ
/μΈ‘μ νλͺ©', async () => {
await cur().getByRole('button', { name: 'Anomaly Detection', exact: true }).click();
await expect.poll(() => api.filter((a) => /options|measurement/.test(a.url)).length, { timeout: 30_000 }).toBeGreaterThan(1);
await expect.poll(() => api.filter((a) => a.method === 'GET' && /settings$/.test(a.url)).length, { timeout: 30_000 }).toBeGreaterThan(0);
for (const a of api.filter((x) => /options|measurement/.test(x.url))) test.info().annotations.push({ type: 'api', description: `${a.status} ${a.url} ${JSON.stringify(a.json?.data ?? '').slice(0, 300)}` });
test.info().annotations.push({ type: 'settings-before', description: settingsList().map((s) => `${s.seq}:${s.ns_id}/${s.infra_id}/${s.node_id} ${s.measurement} ${s.execution_interval}`).join(', ') });
// μ΄μ μ€νμ΄ λ¨κΈ΄ κ°μ μ€μ (μ΄ spec μ μ© λμ)μ λ¨Όμ μ§μ΄λ€ β νκ° μλ΅λ³΄λ€ λ¦κ² κ·Έλ €μ§λ―λ‘ API λͺ©λ‘μΌλ‘ νλ¨ ν νμ κΈ°λ€λ¦°λ€
if (settingsList().some((s) => s.infra_id === OBS_INFRA && s.node_id === NODE && s.measurement === 'mem')) {
test.info().annotations.push({ type: 'frames', description: page.frames().map((x) => x.url().replace(/^https?:\/\/[^/]+/, '')).join(' , ') + ' / rows: ' + (await cur().locator('tr').allInnerTexts()).map(clean).join(' || ') });
await expect(rowOf().first()).toBeVisible({ timeout: 30_000 });
await rowOf().first().getByRole('button', { name: 'Delete' }).click();
await expect(rowOf()).toHaveCount(0, { timeout: 30_000 });
test.info().annotations.push({ type: 'precondition', description: `μ΄μ μ€νμ΄ λ¨κΈ΄ ${OBS_INFRA}/${NODE} mem 5m μ€μ μμ ` });
}
});
await test.step('3. + New Setting β mem Β· node Β· 5m β Create', async () => {
await cur().getByRole('button', { name: '+ New Setting' }).click();
await sel('Scope β Infra').selectOption(OBS_INFRA);
await expect(sel('Scope β Node')).toBeEnabled({ timeout: 30_000 });
await expect.poll(async () => (await sel('Scope β Node').locator('option').allInnerTexts()).join('|'), { timeout: 30_000 }).toContain(NODE);
const opt = (await sel('Scope β Node').locator('option').allInnerTexts()).map((s) => s.trim()).find((s) => s === NODE || s.startsWith(`${NODE} `))!;
await sel('Scope β Node').selectOption({ label: opt });
await sel('Measurement').selectOption('mem');
await sel('Execution Interval').selectOption('5m');
await cur().getByRole('button', { name: 'Create', exact: true }).click();
await expect.poll(() => api.find((a) => a.method === 'POST')?.status ?? 0, { message: 'μ€μ μμ± POST', timeout: 30_000 }).toBeGreaterThan(0);
const p = api.find((a) => a.method === 'POST')!;
created = p.status < 300;
test.info().annotations.push({ type: 'create', description: `${p.status} POST ${p.url} ${JSON.stringify(p.json ?? '').slice(0, 300)}` });
expect(p.status, 'μμ± 2xx').toBeLessThan(300);
});
await test.step('4. μ€μ λͺ©λ‘ λ°μ', async () => {
if (!(await rowOf().first().isVisible({ timeout: 15_000 }).catch(() => false))) {
// 2026-10-16 1μ°¨ μ€ν: μμ± 200('Target Registered Successfully') ν λͺ©λ‘μ΄ λ°λ‘ κ°±μ λμ§ μμ β νλ©΄μ λ€μ μ΄μ΄ νμΈ
test.info().annotations.push({ type: 'list-refresh', description: `μμ± ν 15μ΄ μμ λͺ©λ‘ λ―Έλ°μ(νλ©΄: ${clean(await cur().locator('table').first().innerText().catch(() => '')).slice(0, 200)}) β νλ©΄ λ€μ μ΄κΈ°` });
f0 = await openObs(page, 'insight');
}
await expect(rowOf().first()).toBeVisible({ timeout: 30_000 });
test.info().annotations.push({ type: 'row', description: clean(await rowOf().first().innerText()) });
const s = settingsList().find((x) => x.infra_id === OBS_INFRA && x.node_id === NODE && x.measurement === 'mem');
test.info().annotations.push({ type: 'setting', description: JSON.stringify(s ?? null) });
expect(s?.seq, 'settingSeq').toBeTruthy();
});
await test.step('5. (μ 리) μμ ', async () => {
const seq = settingsList().find((x) => x.infra_id === OBS_INFRA && x.node_id === NODE && x.measurement === 'mem')?.seq;
await rowOf().first().getByRole('button', { name: 'Delete' }).click();
const mine = () => api.find((a) => a.method === 'DELETE' && a.url.endsWith(`/settings/${seq}`));
await expect.poll(() => mine()?.status ?? 0, { message: `μ€μ #${seq} μμ DELETE`, timeout: 30_000 }).toBeGreaterThan(0);
const d = mine()!;
test.info().annotations.push({ type: 'delete', description: `${d.status} DELETE ${d.url}` });
expect(d.status).toBeLessThan(300);
await expect(rowOf()).toHaveCount(0, { timeout: 30_000 });
created = false;
});
} finally {
if (created && !(await rowOf().first().isVisible().catch(() => false))) f0 = await openObs(page, 'insight').catch(() => f0);
if (created && await rowOf().first().isVisible().catch(() => false)) { // μ€ν¨ μ μ 리
await rowOf().first().getByRole('button', { name: 'Delete' }).click().catch(() => {});
await page.waitForTimeout(5000);
test.info().annotations.push({ type: 'cleanup', description: `finally μμ μλ β λ¨μ ν ${await rowOf().count()}` });
}
}
});tests/iv7/IV7_S24.spec.ts Β· 38μ€ Β· ν΄μ eb5eb930 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S24: TC-OBS-024 Anomaly History μ‘°ν [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-INSIGHT-02 β κΈ°μ‘΄ spec(mcmp-e2e deploy/scenarios/shared/step-handlers/obs/insight.ts runObsInsight02)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Insight β Anomaly Detection ν β Anomaly Detection History
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. Anomaly Detection μ€μ μ‘΄μ¬(μ€μ ν λ°μ΄ν° λμ ) β 2026-10-14 κΈ°μ‘΄ μ€μ default/mc1/g1-1 cpu 30m
// μ μ°¨: 1.Insight μ§μ
2.History μμ Infra Β· Node Β· Measurement μ ν 3.Load History 4.μ΄λ ₯ μ‘°ν(GET /api/o11y/insight/anomaly-detection/ns/{ns}/infra/{infra}/node/{node}/history?measurement=β¦) 5.anomaly_score μ°¨νΈ
// κΈ°λκ²°κ³Ό: {values:[{timestamp, anomaly_score}]}, μ΄μμΉ μ μ μ°¨νΈ
// λΉκ³ : μ€μ μ μλ‘ λ§λ€μ§ μκ³ κΈ°μ‘΄ μ€μ μ μ΄λ ₯μ μ½λλ€(μ½κΈ° μ μ©)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, selectNear, OBS_INFRA, OBS_NODE, clean } from '../../lib/iv7-obs';
test('IV7_S24 TC-OBS-024 Anomaly History μ‘°ν', async ({ page }) => {
const settings = collectResponses(page, /\/anomaly-detection\/settings$/);
const hist = collectResponses(page, /\/anomaly-detection\/ns\/.+\/history/);
await loginAsAdmin(page);
const f = await openObs(page, 'insight');
await expect.poll(() => settings.length, { timeout: 30_000 }).toBeGreaterThan(0);
const s = (settings.at(-1)!.json?.data ?? []).find((x: any) => x.infra_id === OBS_INFRA && x.node_id === OBS_NODE);
test.info().annotations.push({ type: 'setting', description: JSON.stringify(s ?? null) });
test.skip(!s, `${OBS_INFRA}/${OBS_NODE} Anomaly Detection μ€μ μμ(μ ν TC-OBS-023)`);
await test.step(`2~4. ${OBS_INFRA} / ${OBS_NODE} / ${s.measurement} β Load History`, async () => {
await f.locator('select').filter({ has: f.locator('option', { hasText: 'Select Infra / Cluster' }) }).first().selectOption({ label: OBS_INFRA });
await page.waitForTimeout(2000);
await f.locator('select').filter({ has: f.locator('option', { hasText: 'Select Node' }) }).first().selectOption({ label: OBS_NODE });
await f.locator('select').filter({ has: f.locator('option', { hasText: 'Select Measurement' }) }).first().selectOption({ label: s.measurement });
await f.getByRole('button', { name: 'Load History' }).click();
await expect.poll(() => hist.length, { timeout: 30_000 }).toBeGreaterThan(0);
});
await test.step('5. anomaly_score κ° Β· μ°¨νΈ', async () => {
const vals = (hist.at(-1)!.json?.data?.values ?? hist.at(-1)!.json?.values ?? []).filter((v: any) => v.anomaly_score !== null && !Number.isNaN(Number(v.anomaly_score)));
test.info().annotations.push({ type: 'history', description: `${hist.at(-1)!.status} ${hist.at(-1)!.url.slice(0, 160)} / κ° ${vals.length}κ° μ: ${JSON.stringify(vals.slice(0, 2))}` });
expect(hist.at(-1)!.status).toBe(200);
expect(vals.length, 'anomaly_score κ°').toBeGreaterThan(0);
await expect(f.locator('svg:visible, canvas:visible').first()).toBeVisible();
test.info().annotations.push({ type: 'screen', description: clean(await f.locator('body').innerText()).match(/Anomaly Detection History.{0,300}/)?.[0] ?? '' });
});
});tests/iv7/IV7_S25.spec.ts Β· 63μ€ Β· ν΄μ 16450ba8 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S25: TC-OBS-025 Prediction (μμΈ‘) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-INSIGHT-03
// νλ©΄: Monitorings > Insight β Prediction ν
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. μμ§ λ°μ΄ν°, insight μμ§ κΈ°λ
// μ μ°¨: 1.Prediction ν 2.μ΅μ
(GET β¦/predictions/options) 3.InfraΒ·NodeΒ·μΈ‘μ νλͺ© cpu μ ν β μμΈ‘ μ€ν(POST β¦/predictions/ns/{ns}/infra/{infra}/node/{node}) 4.μμΈ‘ μ΄λ ₯(GET β¦/history) 5.predicted_value μ°¨νΈ(κ³Όκ±°+λ―Έλ, CPU λ 100-usage_idle)
// κΈ°λκ²°κ³Ό: {values:[{timestamp, predicted_value}]}, μμΈ‘ μ°¨νΈ
// μ€ν 보λ₯(μ¬μ©μ κ²°μ 2026-10-14: μνλ₯Ό λ°κΎΈλ OBS TC λ spec λ§ μμ±). OBS_WRITE=1 μΌ λλ§ μ€ν.
// 2026-10-16 μμ (첫 μ€ν, rf-obs2): μΆμ μ
λ ν° κ΅μ²΄ β Prediction ν νΌμ <label>(Scope β Infra / Cluster *, Scope β Node *, Measurement *, Prediction Range *) λ€μ select,
// λ²νΌ 'Run Prediction' / 'Load Saved'. νλ©΄ μλ΄: 'Predictions become available about one day after the monitoring agent is installed'. μμΈ‘ λ²μλ OBS_PRED_RANGE(κΈ°λ³Έ '1 day').
// μμΈ‘ POST μλ΅Β·μ΄λ ₯ μλ΅μ λͺ¨λ κΈ°λ‘(μ€λ₯ μλ΅μ΄λ©΄ κ·Έ λ΄μ©μΌλ‘ νμ ). μ€μ μλ΅ κ° νλλ 'value'(μνΈ κΈ°λ 'predicted_value') β λ λ€ νμ©
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, clean, OBS_INFRA, OBS_NODE } from '../../lib/iv7-obs';
const NODE = process.env.OBS_TARGET_NODE ?? OBS_NODE;
test('IV7_S25 TC-OBS-025 Prediction (μμΈ‘)', async ({ page }) => {
test.skip(process.env.OBS_WRITE !== '1', 'μν λ³κ²½ TC β OBS_WRITE=1 μΌ λλ§ μ€ν(μ¬μ©μ κ²°μ 2026-10-14)');
const api = collectResponses(page, /\/insight\/predictions/);
await loginAsAdmin(page);
const f = await openObs(page, 'insight');
const sel = (label: string) => f.locator(`xpath=//label[starts-with(normalize-space(),"${label}")]/following::select[1]`).first();
await test.step('1~2. Prediction ν Β· μ΅μ
', async () => {
await f.getByRole('button', { name: 'Prediction', exact: true }).click();
await expect.poll(() => api.filter((a) => /options/.test(a.url)).length, { timeout: 30_000 }).toBeGreaterThan(0);
const o = api.find((a) => /options/.test(a.url))!;
test.info().annotations.push({ type: 'options', description: `${o.status} ${JSON.stringify(o.json?.data ?? '').slice(0, 300)}` });
test.info().annotations.push({ type: 'notice', description: clean(await f.locator('body').innerText()).match(/Predictions become available[^.]*\./)?.[0] ?? '' });
});
await test.step('3. InfraΒ·NodeΒ·cpu μ ν β Run Prediction', async () => {
await sel('Scope β Infra').selectOption(OBS_INFRA);
await expect(sel('Scope β Node')).toBeEnabled({ timeout: 30_000 });
await expect.poll(async () => (await sel('Scope β Node').locator('option').allInnerTexts()).join('|'), { timeout: 30_000 }).toContain(NODE);
const opt = (await sel('Scope β Node').locator('option').allInnerTexts()).map((s) => s.trim()).find((s) => s === NODE || s.startsWith(`${NODE} `))!;
await sel('Scope β Node').selectOption({ label: opt });
await sel('Measurement').selectOption('cpu');
await sel('Prediction Range').selectOption({ label: process.env.OBS_PRED_RANGE ?? '1 day' });
await f.getByRole('button', { name: 'Run Prediction' }).click();
await expect.poll(() => api.find((a) => a.method === 'POST')?.status ?? 0, { message: 'μμΈ‘ POST', timeout: 180_000 }).toBeGreaterThan(0);
const p = api.find((a) => a.method === 'POST')!;
test.info().annotations.push({ type: 'predict', description: `${p.status} POST ${p.url} ${JSON.stringify(p.json ?? '').slice(0, 600)}` });
await page.waitForTimeout(5000);
test.info().annotations.push({ type: 'screen', description: clean(await f.locator('body').innerText()).slice(0, 800) });
expect(p.status, 'μμΈ‘ μ€ν 2xx').toBeLessThan(300);
});
await test.step('4~5. μμΈ‘ κ° Β· μ°¨νΈ', async () => {
const p = api.find((a) => a.method === 'POST')!;
let vals: any[] = p.json?.data?.values ?? [];
if (!vals.length) { // μλ΅μ κ°μ΄ μμΌλ©΄ μ μ₯λ μμΈ‘(μ΄λ ₯) μ‘°ν
await f.getByRole('button', { name: 'Load Saved' }).click();
await expect.poll(() => api.filter((a) => /history/.test(a.url)).length, { timeout: 60_000 }).toBeGreaterThan(0);
const h = api.filter((a) => /history/.test(a.url)).at(-1)!;
test.info().annotations.push({ type: 'history', description: `${h.status} GET ${h.url} ${JSON.stringify(h.json ?? '').slice(0, 600)}` });
vals = h.json?.data?.values ?? [];
}
test.info().annotations.push({ type: 'values', description: `${vals.length}건 ${JSON.stringify(vals.slice(0, 3))}` });
// 2026-10-16: μ€μ μλ΅ νλλ predicted_value κ° μλλΌ value({timestamp, value}) β λ λ€ νμ©νκ³ μ°¨μ΄λ λΉκ³ μ κΈ°λ‘
const pv = (v: any) => v.predicted_value ?? v.value;
test.info().annotations.push({ type: 'field', description: `κ° νλ: ${Object.keys(vals[0] ?? {}).join(',')}` });
expect(vals.filter((v) => pv(v) !== null && pv(v) !== undefined && !Number.isNaN(Number(pv(v)))).length, 'μμΈ‘ κ°(predicted_value/value)').toBeGreaterThan(0);
await expect(f.locator('svg:visible, canvas:visible').first()).toBeVisible();
});
});tests/iv7/IV7_S26.spec.ts Β· 28μ€ Β· ν΄μ 0a66fffe Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S26: TC-OBS-026 Loki λΌλ²¨ μ‘°ν [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-LOG-01 β κΈ°μ‘΄ spec(mcmp-e2e deploy/scenarios/shared/step-handlers/obs/log.ts runObsLog01)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Logs (iframe /embed/logs/{ns}) β Workload(Infra/Cluster)Β·Server μ
λ ν°
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. Loki/fluent-bit κΈ°λΒ·λ‘κ·Έ μμ§(λμ node Log Agent SUCCESS)
// μ μ°¨: 1.Logs μ§μ
2.λΌλ²¨ ν€Β·κ°(GET /api/o11y/log/labels, /labels/{label}/values β νλ©΄μ΄ λΆλ₯΄λ κ²½μ°) 3.Workload μμ λμ Infra μ ν β Server μ
λ ν°μ ν΄λΉ Infra μ node λ°μ
// κΈ°λκ²°κ³Ό: {labels:[NS_ID, INFRA_ID, NODE_ID, level, service ...]}, μ
λ ν°μ λΌλ²¨/κ° λ°μ
// λΉκ³ : 2026-10-14 v0.7 νλ©΄μ Workload λͺ©λ‘μ Tumblebug Infra λͺ©λ‘μΌλ‘ μ±μ°κ³ labels API λ₯Ό λΆλ₯΄μ§ μμ μ μλ€ β νΈμΆ μ¬λΆλ κΈ°λ‘νκ³ , μ
λ ν° κ° λ°μμΌλ‘ νμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, OBS_INFRA, clean } from '../../lib/iv7-obs';
test('IV7_S26 TC-OBS-026 Loki λΌλ²¨ μ‘°ν', async ({ page }) => {
const labels = collectResponses(page, /\/api\/o11y\/log\/labels/);
const infra = collectResponses(page, new RegExp(`/tumblebug/ns/[^/]+/infra/${OBS_INFRA}$`));
await loginAsAdmin(page);
const f = await openObs(page, 'logs');
await test.step(`1~3. Workload ${OBS_INFRA} β Server μ
λ ν°`, async () => {
await f.locator('select').first().selectOption({ label: OBS_INFRA });
await page.waitForTimeout(3000);
const servers = (await f.locator('select').nth(1).locator('option').allInnerTexts()).map(clean).filter((o) => o !== 'All');
const nodes = (infra.at(-1)?.json?.node ?? []).map((n: any) => n.id);
test.info().annotations.push({ type: 'servers', description: `νλ©΄ ${servers.join(', ')} / Infra node ${nodes.join(', ')}` });
test.info().annotations.push({ type: 'labels-api', description: labels.map((l) => `${l.status} ${l.url} ${JSON.stringify(l.json?.data ?? l.json).slice(0, 200)}`).join(' | ') || '(νΈμΆ μμ)' });
expect(servers.length).toBeGreaterThan(0);
if (nodes.length) expect(servers.sort()).toEqual([...nodes].sort());
});
});tests/iv7/IV7_S27.spec.ts Β· 50μ€ Β· ν΄μ 255e0125 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S27: TC-OBS-027 Loki LogQL ν€μλ κ²μ [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-LOG-02 β κΈ°μ‘΄ spec(mcmp-e2e deploy/scenarios/shared/step-handlers/obs/log.ts runObsLog02)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Logs β Workload Β· Server Β· Keyword β Search β List of Log
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. TC-OBS-026(IV7_S26)
// μ μ°¨: 1.Workload(OBS_INFRA)Β·Server All μ ν ν Keyword μ
λ ₯ β Search 2.LogQL {NS_ID="β¦",INFRA_ID="β¦"} |~ "(?i)keyword" 3.λ²μ μ‘°ν(GET /api/o11y/log/query_range?query=β¦) 4.λ‘κ·Έ ν(Timestamp/Level/Node/Service/Message, μκ°μ λ‘컬)
// μ
λ ₯κ°(μνΈ): Keyword error (κ²°κ³Όκ° μμΌλ©΄ ν€μλ μμ΄ κ²μν΄ νΒ·μκ° νμλ₯Ό νμΈ)
// κΈ°λκ²°κ³Ό: LogSummaryDto.ResultDto(data[], stats), λ‘κ·Έ ν
μ΄λΈ(μκ°μ λ‘컬)
// λΉκ³ : 2026-10-14 κ΄μ°° β ν Timestamp μ°λκ° 2001 λ‘ νμλ¨(μλ΅ μκ°κ³Ό λΉκ΅ν΄ νμ )
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, OBS_INFRA, clean } from '../../lib/iv7-obs';
test('IV7_S27 TC-OBS-027 Loki LogQL ν€μλ κ²μ', async ({ page }) => {
const q = collectResponses(page, /\/api\/o11y\/log\/query_range/);
await loginAsAdmin(page);
const f = await openObs(page, 'logs');
await f.locator('select').first().selectOption({ label: OBS_INFRA });
await page.waitForTimeout(2000);
const search = async (kw: string) => {
const n0 = q.length;
const box = f.getByPlaceholder(/keyword/i).first().or(f.locator('input[type="text"], input[type="search"]').first());
await box.fill(kw);
await f.getByRole('button', { name: 'Search', exact: true }).click();
await expect.poll(() => q.length, { timeout: 30_000 }).toBeGreaterThan(n0);
await page.waitForTimeout(2000);
return q.at(-1)!;
};
let r = await test.step('1~3. Keyword error β Search β LogQL query_range', () => search('error'));
const query = decodeURIComponent(r.url.match(/query=([^&]+)/)?.[1] ?? '').replace(/\+/g, ' ');
test.info().annotations.push({ type: 'logql', description: `${query} / 결과 ${(r.json?.data?.data ?? []).length}건` });
expect(query).toContain(`INFRA_ID="${OBS_INFRA}"`);
expect(query).toMatch(/\|~ "\(\?i\)error"/);
if (!(r.json?.data?.data ?? []).length) {
r = await test.step('(error κ²°κ³Ό μμ) ν€μλ μμ΄ κ²μ β ν νμΈ', () => search(''));
}
await test.step('4. λ‘κ·Έ ν Β· Timestamp(λ‘컬)', async () => {
const rows: any[] = r.json?.data?.data ?? [];
test.info().annotations.push({ type: 'rows', description: `${rows.length}건 μ: ${JSON.stringify(rows[0] ?? {}).slice(0, 300)}` });
expect(rows.length).toBeGreaterThan(0);
const body = clean(await f.locator('body').innerText());
for (const h of ['Timestamp', 'Level', 'Node', 'Service', 'Message']) expect(body).toContain(h);
const shown = body.match(/List of Log.*?Message (\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}:\d{2})/);
const first = rows[0] ?? {};
const apiTs = first.timestamp ?? first.time ?? first.ts ?? first.values?.[0]?.[0];
const apiDate = apiTs ? new Date(/^\d{16,19}$/.test(String(apiTs)) ? Number(String(apiTs).slice(0, 13)) : apiTs) : undefined;
test.info().annotations.push({ type: 'timestamp', description: `νλ©΄ 첫 ν ${shown ? shown.slice(1, 5).join('-') : '(μμ)'} / μλ΅ μ²« ν ${apiTs} β ${apiDate?.toISOString?.() ?? '?'}` });
expect(Number(shown?.[1]), 'νμ μ°λ = μλ΅ μκ°μ μ°λ').toBe(apiDate ? apiDate.getFullYear() : new Date().getFullYear());
});
});tests/iv7/IV7_S28.spec.ts Β· 44μ€ Β· ν΄μ ee5cfc00 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S28: TC-OBS-028 manager trace μ‘°ν (Tempo) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-TRACE-01 β κΈ°μ‘΄ spec(mcmp-e2e deploy/scenarios/shared/step-handlers/obs/trace.ts runObsTrace01)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Tracing (iframe /embed/trace/{ns}) β Scope Framework / VM, Service, Keyword, Range β Search β List of Trace
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. Tempo κΈ°λΒ·νΈλ μ΄μ€ μμ§(Framework λ o11y manager/insight μ체 κ³μΈ‘)
// μ μ°¨: 1.Tracing μ§μ
2.Scope Framework 3.Service λͺ©λ‘(GET /api/o11y/trace/services?scope=framework) Β· Service mc-observability-manager Β· Range 1H β Search 4.νΈλ μ΄μ€ κ²μ(GET /api/o11y/trace/search?scope=β¦&service=β¦) 5.List of Trace ν(Start Time/Root Service/Root Name/Duration/Trace ID)
// κΈ°λκ²°κ³Ό: TraceSummary[](traceId, rootService, rootName, startTimeMs, durationMs), List of Trace νΒ·νμ΄μ
// λΉκ³ : VM scope λ λμ VM μ Beyla/OTel(trace agent) μ΄ μμ΄μΌ ν¨(2026-10-14 μ node trace_agent NOT_INSTALLED) β Framework λ‘ νμ , VM μ κΈ°λ‘
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, clean } from '../../lib/iv7-obs';
test('IV7_S28 TC-OBS-028 manager trace μ‘°ν (Tempo)', async ({ page }) => {
const svc = collectResponses(page, /\/api\/o11y\/trace\/services/);
const search = collectResponses(page, /\/api\/o11y\/trace\/search/);
await loginAsAdmin(page);
const f = await openObs(page, 'trace');
await test.step('2~3. Framework Β· Service λͺ©λ‘ Β· mc-observability-manager Β· 1H β Search', async () => {
await f.getByRole('button', { name: 'Framework', exact: true }).click();
await expect.poll(() => svc.length, { timeout: 30_000 }).toBeGreaterThan(0);
test.info().annotations.push({ type: 'services', description: JSON.stringify(svc.at(-1)!.json?.data) });
await f.locator('select').first().selectOption({ label: 'mc-observability-manager' });
await f.locator('select').nth(1).selectOption({ label: '1H' });
await f.getByRole('button', { name: 'Search', exact: true }).click();
await expect.poll(() => search.length, { timeout: 30_000 }).toBeGreaterThan(0);
});
await test.step('4~5. κ²μ μλ΅ Β· List of Trace ν', async () => {
const r = search.at(-1)!;
const list: any[] = r.json?.data ?? [];
test.info().annotations.push({ type: 'search', description: `${r.url.slice(0, 180)} β ${list.length}건 μ: ${JSON.stringify(list[0] ?? {}).slice(0, 250)}` });
expect(r.url).toMatch(/scope=framework/);
expect(r.url).toMatch(/service=mc-observability-manager/);
expect(list.length).toBeGreaterThan(0);
expect(list.every((t) => t.rootService === 'mc-observability-manager')).toBe(true);
const body = clean(await f.locator('body').innerText());
for (const h of ['Start Time', 'Root Service', 'Root Name', 'Duration', 'Trace ID']) expect(body).toContain(h);
expect(body).toMatch(/List of Trace Β· \d+ traces/);
});
await test.step('(κΈ°λ‘) VM scope', async () => {
await f.getByRole('button', { name: 'VM', exact: true }).click();
await page.waitForTimeout(3000);
test.info().annotations.push({ type: 'vm-scope', description: clean(await f.locator('body').innerText()).slice(0, 300) });
});
});tests/iv7/IV7_S29.spec.ts Β· 35μ€ Β· ν΄μ 1067e8f8 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S29: TC-OBS-029 Grafana Explore νΈλ μ΄μ± (νΈλ μ΄μ€ μμΈ Call Sequence) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-TRACE-02 β κΈ°μ‘΄ spec(mcmp-e2e deploy/scenarios/shared/step-handlers/obs/trace.ts runObsTrace02)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: Monitorings > Tracing β List of Trace ν ν΄λ¦ β κ°μ ν μλ API Call Sequence μΈλΌμΈ νΌμΉ¨
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. TC-OBS-028(IV7_S28) β λͺ©λ‘μμ μ ν
// μ μ°¨: 1.Framework κ²μ 2.λͺ©λ‘ 첫 ν ν΄λ¦ 3.λ¨μΌ νΈλ μ΄μ€(GET /api/o11y/trace/{traceId}) β Call Sequence(Span/Service/Kind/Offset/Duration/Timeline) 4.ν λ€μ ν΄λ¦ β μ ν
// κΈ°λκ²°κ³Ό: TraceDetail(spans[]), μΈλΌμΈ Call Sequence
// λΉκ³ : TC μ΄λ¦μ 'Grafana Explore' μ΄λ μνΈ μ μ°¨λ o11y νλ©΄μ μΈλΌμΈ Call Sequence(2026-10-14 μνΈ κΈ°μ€)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, collectResponses, clean } from '../../lib/iv7-obs';
test('IV7_S29 TC-OBS-029 Grafana Explore νΈλ μ΄μ± (Call Sequence)', async ({ page }) => {
const detail = collectResponses(page, /\/api\/o11y\/trace\/[0-9a-f]{16,}$/);
await loginAsAdmin(page);
const f = await openObs(page, 'trace');
await f.getByRole('button', { name: 'Search', exact: true }).click();
const row = f.locator('tbody tr').first();
await expect(row).toBeVisible({ timeout: 30_000 });
const traceIdShort = (clean(await row.innerText()).match(/([0-9a-f]{16})β¦?$/) ?? [])[1] ?? '';
await test.step('2~3. 첫 ν ν΄λ¦ β λ¨μΌ νΈλ μ΄μ€ Β· Call Sequence', async () => {
await row.click();
await expect.poll(() => detail.length, { timeout: 30_000 }).toBeGreaterThan(0);
const d = detail.at(-1)!.json?.data;
test.info().annotations.push({ type: 'detail', description: `${d?.traceId} spans ${(d?.spans ?? []).length}: ${(d?.spans ?? []).slice(0, 4).map((s: any) => `${s.service}/${s.name}/${s.kind}`).join(' | ')}` });
expect(d?.traceId?.startsWith(traceIdShort)).toBe(true);
expect((d?.spans ?? []).length).toBeGreaterThan(0);
await expect(f.getByText('API Call Sequence').first()).toBeVisible();
for (const h of ['Span (call order)', 'Service', 'Kind', 'Offset', 'Duration', 'Timeline']) await expect(f.getByText(h, { exact: true }).first()).toBeVisible();
});
await test.step('4. λ€μ ν΄λ¦ β μ ν', async () => {
await row.click();
await expect(f.getByText('API Call Sequence')).toHaveCount(0, { timeout: 10_000 });
});
});tests/iv7/IV7_S30.spec.ts Β· 32μ€ Β· ν΄μ 211db6a2 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S30: TC-OBS-030 Grafana μλ² λ (Trace μλ² λ νλ©΄) [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-TRACE-03 β κΈ°μ‘΄ spec(mcmp-e2e mc-web-console/specs/obs/TC-OBS-TRACE-03-trace-iframe.spec.ts)μ μ μ°¨λ§ μ°Έκ³ (μ¬μ©μ κ²°μ 2026-10-14)
// νλ©΄: mc-web-console Monitorings > Tracing β iframe /embed/trace/{ns}
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. TC-OBS-028, mc-web-console μ°λ μ€μ
// μ μ°¨: 1./embed/trace/{ns} λ‘λ(μ½μ iframe) 2.EmbedLayout(λ€λΉ/μ¬μ΄λλ° μλ μ 체νλ©΄) 3.μλ² λ νλ©΄μμ Scope/Service/κ²μ Β· Call Sequence λμ
// κΈ°λκ²°κ³Ό: λ€λΉ μλ μλ² λ Trace νλ©΄, iframe λ΄ κ²μ/μμΈ μ μ λμ
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, OBS_NS, clean } from '../../lib/iv7-obs';
test('IV7_S30 TC-OBS-030 Grafana μλ² λ (Trace μλ² λ νλ©΄)', async ({ page }) => {
await loginAsAdmin(page);
const f = await openObs(page, 'trace');
await test.step('1~2. /embed/trace κ²½λ‘ Β· μλ² λ λ μ΄μμ', async () => {
const src = await page.locator('iframe').first().getAttribute('src');
test.info().annotations.push({ type: 'iframe', description: `src=${src} / frame=${f.url()} / navΒ·sidebar ${await f.locator('nav, aside, .sidebar').count()}` });
expect(new URL(f.url()).pathname).toBe(`/embed/trace/${OBS_NS}`);
expect(await f.locator('nav, aside, .sidebar').count()).toBe(0);
});
await test.step('3. Scope μ ν Β· κ²μ Β· Call Sequence', async () => {
await f.getByRole('button', { name: 'VM', exact: true }).click();
await page.waitForTimeout(1500);
await f.getByRole('button', { name: 'Framework', exact: true }).click();
await f.getByRole('button', { name: 'Search', exact: true }).click();
const row = f.locator('tbody tr').first();
await expect(row).toBeVisible({ timeout: 30_000 });
await row.click();
await expect(f.getByText('API Call Sequence').first()).toBeVisible({ timeout: 30_000 });
test.info().annotations.push({ type: 'screen', description: clean(await f.locator('body').innerText()).match(/List of Trace.{0,250}/)?.[0] ?? '' });
});
});tests/iv7/IV7_S31.spec.ts Β· 25μ€ Β· ν΄μ 146ac1b8 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S31: TC-OBS-031 Infra / NS λ 벨 ν€μλ μμ΄ μ 체 λ‘κ·Έ μ‘°ν [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-LOG-01 β 'ν
μ€νΈ μλ리μ€' μνΈμ λ‘κ·Έ TC(μλ TC-OBS-026). Framework μνΈ TC-OBS-026(Loki λΌλ²¨ μ‘°ν)κ³Ό μ μκ° λ¬λΌ TC-OBS-031 λ‘ λ³λ λΆμ¬(μ¬μ©μ κ²°μ 2026-10-15)
// νλ©΄: Monitorings > Logs (iframe /embed/logs/{ns}) β Workload(Infra) Β· Server All Β· Keyword λΉμ β Search
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ Infra(OBS_INFRA, κΈ°λ³Έ mc1) Log Agent μμ§
// μ μ°¨: 1.Logs μ§μ
2.Workload mc1 Β· Server All Β· Keyword μμ΄ Search 3.LogQL {NS_ID,INFRA_ID} (ν€μλ νν° μμ) 4.λ‘κ·Έ ν(Timestamp/Level/Node/Service/Message) Β· μ¬λ¬ node λ‘κ·Έ
// κΈ°λκ²°κ³Ό: Infra λ²μμ μ 체 λ‘κ·Έκ° ν€μλ νν° μμ΄ νμλλ€
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, OBS_NS, OBS_INFRA } from '../../lib/iv7-obs';
import { logSearch } from '../../lib/iv7-obs-logs';
test('IV7_S31 TC-OBS-031 Infra / NS λ 벨 ν€μλ μμ΄ μ 체 λ‘κ·Έ μ‘°ν', async ({ page }) => {
await loginAsAdmin(page);
const f = await openObs(page, 'logs');
const { query, rows, body } = await logSearch(page, f, OBS_INFRA, '');
expect(query).toContain(`NS_ID="${OBS_NS}"`);
expect(query).toContain(`INFRA_ID="${OBS_INFRA}"`);
expect(query, 'ν€μλ νν° μμ').not.toMatch(/\|~/);
expect(rows.length).toBeGreaterThan(0);
for (const h of ['Timestamp', 'Level', 'Node', 'Service', 'Message']) expect(body).toContain(h);
const nodes = [...new Set(rows.map((x) => x.labels?.NODE_ID).filter(Boolean))];
test.info().annotations.push({ type: 'nodes', description: nodes.join(', ') });
expect(rows.every((x) => x.labels?.INFRA_ID === OBS_INFRA), 'λͺ¨λ λμ Infra λ‘κ·Έ').toBe(true);
});tests/iv7/IV7_S32.spec.ts Β· 23μ€ Β· ν΄μ 6e53aa33 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S32: TC-OBS-032 Infra / NS λ 벨 ν€μλ κΈ°λ° λ‘κ·Έ μ‘°ν [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-LOG-02 β 'ν
μ€νΈ μλ리μ€' μνΈμ λ‘κ·Έ TC(μλ TC-OBS-027). Framework μνΈ TC-OBS-027(Loki LogQL ν€μλ κ²μ)κ³Ό μ μκ° λ¬λΌ TC-OBS-032 λ‘ λ³λ λΆμ¬(μ¬μ©μ κ²°μ 2026-10-15)
// νλ©΄: Monitorings > Logs β Workload(Infra) Β· Server All Β· Keyword β Search
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/default. λμ Infra Log Agent μμ§
// μ μ°¨: 1.Workload mc1 Β· Server All Β· Keyword error β Search 2.LogQL |~ "(?i)error" 3.κ²°κ³Ό λ‘κ·Έκ° λͺ¨λ ν€μλλ₯Ό ν¬ν¨
// κΈ°λκ²°κ³Ό: Infra λ²μ λ‘κ·Έ μ€ ν€μλλ₯Ό ν¬ν¨ν λ‘κ·Έλ§ νμλλ€
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, OBS_INFRA } from '../../lib/iv7-obs';
import { logSearch } from '../../lib/iv7-obs-logs';
const KW = process.env.OBS_LOG_KEYWORD ?? 'error';
test('IV7_S32 TC-OBS-032 Infra / NS λ 벨 ν€μλ κΈ°λ° λ‘κ·Έ μ‘°ν', async ({ page }) => {
await loginAsAdmin(page);
const f = await openObs(page, 'logs');
const { query, rows } = await logSearch(page, f, OBS_INFRA, KW);
expect(query).toMatch(new RegExp(`\\|~ "\\(\\?i\\)${KW}"`));
expect(rows.length, `ν€μλ ${KW} κ²°κ³Ό`).toBeGreaterThan(0);
const miss = rows.filter((x) => !new RegExp(KW, 'i').test(JSON.stringify(x.value ?? x.line ?? x)));
test.info().annotations.push({ type: 'keyword', description: `${rows.length}건 μ€ ν€μλ λ―Έν¬ν¨ ${miss.length}건` });
expect(miss.length, 'λͺ¨λ κ²°κ³Όκ° ν€μλ ν¬ν¨').toBe(0);
});tests/iv7/IV7_S33.spec.ts Β· 28μ€ Β· ν΄μ d211f549 Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S33: TC-OBS-033 K8s Cluster ν€μλ μμ΄ μ 체 λ‘κ·Έ μ‘°ν [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-LOG-03 β 'ν
μ€νΈ μλ리μ€' μνΈμ λ‘κ·Έ TC. Framework μνΈμ λ²νΈκ° κ²Ήμ³ TC-OBS-033 μΌλ‘ λ³λ λΆμ¬(μ¬μ©μ κ²°μ 2026-10-15)
// νλ©΄: Monitorings > Logs β Workload μμ K8s Cluster μ ν Β· Keyword β Search
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/{K8s Project}. K8s ν΄λ¬μ€ν° μ‘΄μ¬ Β· λ‘κ·Έ μμ§(K8s Log Agent, TC-OBS-005)
// μ μ°¨: 1.Logs μ§μ
2.Workload μ K8s Cluster(OBS_K8S_CLUSTER) μ ν β Search 3.LogQL Β· λ‘κ·Έ ν
// κΈ°λκ²°κ³Ό: ν΄λ¬μ€ν° λ²μ λ‘κ·Έκ° νμλλ€
// λΉκ³ : K8s ν΄λ¬μ€ν°κ° μμΌλ©΄ SKIP(μ ν λ―ΈμΆ©μ‘±, 2026-10-15 stage ν΄λ¬μ€ν° 0κ°)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, clean } from '../../lib/iv7-obs';
import { logSearch } from '../../lib/iv7-obs-logs';
const NS = process.env.OBS_K8S_NS ?? 'p1';
const CLUSTER = process.env.OBS_K8S_CLUSTER ?? '';
test('IV7_S33 TC-OBS-033 K8s Cluster ν€μλ μμ΄ μ 체 λ‘κ·Έ μ‘°ν', async ({ page }) => {
await loginAsAdmin(page);
const f = await openObs(page, 'logs', NS);
const opts = (await f.locator('select').first().locator('option').allInnerTexts()).map(clean).filter((o) => !/^Select/.test(o));
test.info().annotations.push({ type: 'workloads', description: `${NS}: ${opts.join(', ') || '(μμ)'}` });
const target = CLUSTER || opts[0];
test.skip(!target, `${NS} μ K8s ν΄λ¬μ€ν° μμ(μ ν λ―ΈμΆ©μ‘±)`);
const { query, rows } = await logSearch(page, f, target, '');
// K8s ν΄λ¬μ€ν°λ Infra μ κ°μ λΌλ²¨ 체κ³: {NS_ID, INFRA_ID=<cluster>, NODE_ID=<node>} (2026-10-15 AWS k1s νμΈ)
expect(query).toContain(`INFRA_ID="${target}"`);
expect(rows.length).toBeGreaterThan(0);
expect(JSON.stringify(rows[0])).toContain(`"INFRA_ID":"${target}"`);
});tests/iv7/IV7_S34.spec.ts Β· 36μ€ Β· ν΄μ 2b6c55dc Β· μλλ¦¬μ€ λ³΄κΈ°
// IV7_S34: TC-OBS-034 K8s Cluster ν€μλ κΈ°λ° λ‘κ·Έ μ‘°ν [μ μ, 곡ν΅]
// v0.6 TC-ID: TC-OBS-LOG-04 β 'ν
μ€νΈ μλ리μ€' μνΈμ λ‘κ·Έ TC. Framework μνΈμ λ²νΈκ° κ²Ήμ³ TC-OBS-034 μΌλ‘ λ³λ λΆμ¬(μ¬μ©μ κ²°μ 2026-10-15)
// νλ©΄: Monitorings > Logs β Workload μμ K8s Cluster μ ν Β· Keyword error β Search
// μμ μν: admin(mcmp) λ‘κ·ΈμΈ, ws01/{K8s Project}. K8s ν΄λ¬μ€ν° μ‘΄μ¬ Β· λ‘κ·Έ μμ§(K8s Log Agent, TC-OBS-005)
// μ μ°¨: 1.Logs μ§μ
2.Workload μ K8s Cluster(OBS_K8S_CLUSTER) μ ν Β· Keyword error β Search 3.LogQL Β· λ‘κ·Έ ν
// κΈ°λκ²°κ³Ό: ν΄λ¬μ€ν° λ²μ λ‘κ·Έκ° ν€μλ νν°λ‘ νμλλ€
// λΉκ³ : K8s ν΄λ¬μ€ν°κ° μμΌλ©΄ SKIP(μ ν λ―ΈμΆ©μ‘±, 2026-10-15 stage ν΄λ¬μ€ν° 0κ°)
import { test, expect } from '../../lib/fixtures';
import { loginAsAdmin } from '../../lib/ui';
import { openObs, clean } from '../../lib/iv7-obs';
import { logSearch } from '../../lib/iv7-obs-logs';
const NS = process.env.OBS_K8S_NS ?? 'p1';
const CLUSTER = process.env.OBS_K8S_CLUSTER ?? '';
test('IV7_S34 TC-OBS-034 K8s Cluster ν€μλ κΈ°λ° λ‘κ·Έ μ‘°ν', async ({ page }) => {
await loginAsAdmin(page);
const f = await openObs(page, 'logs', NS);
const opts = (await f.locator('select').first().locator('option').allInnerTexts()).map(clean).filter((o) => !/^Select/.test(o));
test.info().annotations.push({ type: 'workloads', description: `${NS}: ${opts.join(', ') || '(μμ)'}` });
const target = CLUSTER || opts[0];
test.skip(!target, `${NS} μ K8s ν΄λ¬μ€ν° μμ(μ ν λ―ΈμΆ©μ‘±)`);
// ν€μλ κΈ°λ³Έ error(OBS_LOG_KEYWORD λ‘ λ³κ²½). ν΄λ¬μ€ν° λ‘κ·Έμ error κ° μμΌλ©΄(2026-10-15 GKE k4s 0건) νλ³Έ λ‘κ·Έμ μλ λ¨μ΄λ‘ λ€μ κ²μ
let kw = process.env.OBS_LOG_KEYWORD ?? 'error';
let { query, rows } = await logSearch(page, f, target, kw);
if (!rows.length && !process.env.OBS_LOG_KEYWORD) {
const sample = await logSearch(page, f, target, '');
kw = JSON.stringify(sample.rows[0] ?? {}).match(/\b(stderr|stdout|warn\w*|info)\b/i)?.[1] ?? '';
test.info().annotations.push({ type: 'keyword-fallback', description: `error 0건 β νλ³Έ λ‘κ·Έ λ¨μ΄ '${kw}' λ‘ μ¬κ²μ` });
if (kw) ({ query, rows } = await logSearch(page, f, target, kw));
}
// K8s ν΄λ¬μ€ν°λ Infra μ κ°μ λΌλ²¨ 체κ³: {NS_ID, INFRA_ID=<cluster>, NODE_ID=<node>} (2026-10-15 AWS k1s νμΈ)
expect(query).toContain(`INFRA_ID="${target}"`);
expect(rows.length).toBeGreaterThan(0);
expect(JSON.stringify(rows[0])).toContain(`"INFRA_ID":"${target}"`);
});