Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions apps/netscli-gui/e2e/tauri-render/scenarios/helpers/menu.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,16 @@ async function assertInterfaceReadinessReflectsSelection(driver) {
return { found: true, originalName, originalUp, downName };
`);
await closeSettingsDialog(driver);
if (!selection.found) return;
if (!selection.found) {
// A genuine skip, but say so. On a runner with a single healthy NIC
// there is no down interface to select, so this assertion has nothing to
// exercise -- it used to return silently, which reads in the log exactly
// like a pass.
console.log(
'SKIP assertInterfaceReadinessReflectsSelection: no down interface on this host to select',
);
return;
}
await waitForText(driver, '[data-testid="statusbar"]', /Interface down/i);

if (selection.originalName && selection.originalName !== selection.downName) {
Expand Down Expand Up @@ -232,11 +241,16 @@ async function openSettingsDialog(driver) {
}

async function closeSettingsDialog(driver) {
// A missing close button is a failure, not a no-op.
//
// This used to return silently when `.settings-close` was absent, so a
// dialog that could no longer be dismissed left the suite green -- and
// every later step ran against a modal that was still open, producing
// confusing downstream failures instead of naming the real one.
const closeButtons = await driver.findElements(By.css('.settings-close'));
if (closeButtons.length > 0) {
await closeButtons[0].click();
await waitForNoElement(driver, '[data-testid="settings-dialog"]');
}
assert.ok(closeButtons.length > 0, 'settings dialog has no .settings-close button to dismiss it');
await closeButtons[0].click();
await waitForNoElement(driver, '[data-testid="settings-dialog"]');
}

async function openMenu(driver, menuLabel) {
Expand Down
50 changes: 41 additions & 9 deletions apps/netscli-gui/e2e/tauri-render/scenarios/tools.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ export async function exerciseDiscover(driver) {
await assertCommand(driver, /netscli discover 127\.0\.0\.0\/30 --resolve --json/);
await runActiveTool(driver);
await assertOperationToastReturnsToTab(driver, /Discover/i);
await waitForText(driver, '[data-testid="statusbar"]', /\d+ hosts?/i, 25_000);
// At least one, not "0 hosts". 127.0.0.0/30 contains the loopback address
// the probe server runs on, so a working backend always finds it -- and
// /\d+ hosts?/ matched "0 hosts", meaning a total Discover regression went
// green.
await waitForText(driver, '[data-testid="statusbar"]', /[1-9]\d* hosts?/i, 25_000);
await assertNoErrorStrip(driver);
}

Expand All @@ -81,7 +85,14 @@ export async function exerciseSweep(driver, port) {
await replaceInput(driver, '[data-testid="sweep-ports-input"]', String(port));
await assertCommand(driver, new RegExp(`netscli sweep 127\\.0\\.0\\.0\\/30 -p ${port} --resolve --json`));
await runActiveTool(driver);
await waitForText(driver, '[data-testid="statusbar"]', /\d+ hosts? .* \d+ with open ports?/i, 30_000);
// Both counts must be non-zero: the sweep targets the probe server's own
// port on loopback, so a working backend finds a host and an open port.
await waitForText(
driver,
'[data-testid="statusbar"]',
/[1-9]\d* hosts? .* [1-9]\d* with open ports?/i,
30_000,
);
await waitForText(driver, '[data-testid="result-table"]', /MAC/i);
await waitForText(driver, '[data-testid="result-table"]', /RTT/i);
await assertNoErrorStrip(driver);
Expand All @@ -91,7 +102,9 @@ export async function exerciseInterfaces(driver) {
await addToolTab(driver, 'Interfaces');
await withElement(driver, '[data-testid="run-active-tab"]');
await assertCommand(driver, /netscli interfaces --json/);
await waitForText(driver, '[data-testid="statusbar"]', /\d+ interfaces?/i, 20_000);
// Every machine has at least a loopback interface, so zero here is a
// backend failure rather than a legitimate empty result.
await waitForText(driver, '[data-testid="statusbar"]', /[1-9]\d* interfaces?/i, 20_000);
await waitForRow(driver, '[data-testid^="result-row-iface-"]', 20_000);
await waitForText(driver, '[data-testid="result-table"]', /App/i);
await assertKeyboardSelection(driver);
Expand All @@ -102,25 +115,44 @@ export async function exerciseArp(driver) {
await addToolTab(driver, 'ARP Table');
await withElement(driver, '[data-testid="run-active-tab"]');
await assertCommand(driver, /netscli arp --json/);
// Deliberately still tolerates zero, unlike the assertions above. An empty
// neighbour cache is a legitimate state on a freshly booted CI runner, so
// requiring >= 1 here would be flaky rather than strict. A hard backend
// failure is still caught: the status bar would show an error instead of a
// count, and assertNoErrorStrip below would fire.
await waitForText(driver, '[data-testid="statusbar"]', /\d+ ARP entries?/i, 20_000);
await assertNoErrorStrip(driver);
}

export async function exercisePcapValidation(driver) {
await clickButtonText(driver, '.menu-button', 'Tools');
// Packet Capture must always be listed, on every build.
//
// This used to `return false` and pass when the menu item was missing, so
// the entire capture surface disappearing was indistinguishable from a
// healthy skip. It is now a hard failure: builds without the feature keep
// the tool visible and show setup guidance instead of hiding it, so an
// absent entry means something broke.
const pcapVisible = await driver.executeScript(
"return Array.from(document.querySelectorAll('.menu-popover-item')).some((button) => button.textContent.trim() === 'Packet Capture');",
);
if (!pcapVisible) {
await driver.findElement(By.css('.workspace')).click();
await waitForNoElement(driver, '.menu-popover');
return false;
}
assert.ok(
pcapVisible,
'Packet Capture must appear in the Tools menu on every build, with setup guidance when unsupported',
);
await clickButtonText(driver, '.menu-popover-item', 'Packet Capture');
await withElement(driver, '[data-testid="pcap-interface-input"]');
const unavailable = await driver.findElements(By.css('[data-testid="pcap-unavailable-state"]'));
if (unavailable.length > 0) {
await waitForText(driver, '[data-testid="pcap-unavailable-state"]', /Packet Capture/i);
// The expected state on a published build. Assert the guidance actually
// tells the user what to do, rather than just noting the element exists.
const guidance = await unavailable[0].getText();
assert.match(guidance, /packet capture/i, 'unavailable state should name the feature');
assert.match(
guidance,
/install|build|support|npcap|libpcap/i,
'unavailable state should say how to get a capture-capable build, not just that it is missing',
);
await assertNoErrorStrip(driver);
return false;
}
Expand Down
Loading