Skip to content
Open
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
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,6 @@ install: $(DIST_TEST) po/LINGUAS
mkdir -p $(DESTDIR)/usr/lib/systemd/system/
cp src/systemd/webui-cockpit-ws.service $(DESTDIR)/usr/lib/systemd/system/

# required for running integration tests;
TEST_NPMS = \
node_modules/sizzle \
$(NULL)

dist: $(TARFILE)
@ls -1 $(TARFILE)

Expand All @@ -147,7 +142,7 @@ $(TARFILE): $(DIST_TEST) $(SPEC)
tar --xz $(TAR_ARGS) -cf $(TARFILE) --transform 's,^,$(RPM_NAME)/,' \
--exclude '*.in' --exclude test/reference \
$$(git ls-files | grep -v node_modules) \
$(COCKPIT_REPO_FILES) $(NODE_MODULES_TEST) $(SPEC) $(TEST_NPMS) VERSION.txt \
$(COCKPIT_REPO_FILES) $(NODE_MODULES_TEST) $(SPEC) VERSION.txt \
dist/

srpm: $(TARFILE) $(SPEC)
Expand Down
2 changes: 1 addition & 1 deletion node_modules
Submodule node_modules updated 36 files
+12 −19 .package-lock.json
+0 −1 .package.json
+4 −3 @csstools/css-calc/CHANGELOG.md
+5 −0 @csstools/css-calc/dist/index.d.ts
+1 −1 @csstools/css-calc/dist/index.mjs
+1 −1 @csstools/css-calc/package.json
+4 −3 @csstools/css-syntax-patches-for-csstree/CHANGELOG.md
+25 −21 @csstools/css-syntax-patches-for-csstree/dist/index.json
+1 −1 @csstools/css-syntax-patches-for-csstree/package.json
+322 −248 axe-core/axe.js
+2 −2 axe-core/axe.min.js
+7 −0 axe-core/locales/_template.json
+1 −1 axe-core/package.json
+4 −0 axe-core/sri-history.json
+1 −0 lru-cache/dist/esm/browser/diagnostics-channel-browser.d.mts.map
+1 −0 lru-cache/dist/esm/browser/diagnostics-channel-browser.mjs.map
+5 −0 lru-cache/dist/esm/browser/diagnostics-channel.d.ts
+4 −0 lru-cache/dist/esm/browser/diagnostics-channel.js
+1,381 −0 lru-cache/dist/esm/browser/index.d.ts
+1 −0 lru-cache/dist/esm/browser/index.d.ts.map
+1,688 −0 lru-cache/dist/esm/browser/index.js
+1 −0 lru-cache/dist/esm/browser/index.js.map
+2 −0 lru-cache/dist/esm/browser/index.min.js
+7 −0 lru-cache/dist/esm/browser/index.min.js.map
+1 −1 lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map
+1 −1 lru-cache/dist/esm/diagnostics-channel-esm.mjs.map
+1 −1 lru-cache/dist/esm/diagnostics-channel.js
+2 −2 lru-cache/dist/esm/index.min.js.map
+15 −2 lru-cache/package.json
+0 −67 sizzle/AUTHORS.txt
+0 −36 sizzle/LICENSE.txt
+0 −55 sizzle/README.md
+0 −2,514 sizzle/dist/sizzle.js
+0 −3 sizzle/dist/sizzle.min.js
+0 −1 sizzle/dist/sizzle.min.map
+0 −85 sizzle/package.json
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"jed": "1.1.1",
"qunit": "2.25.0",
"sass": "1.98.0",
"sizzle": "2.3.10",
"stdio": "2.1.3",
"stylelint": "17.6.0",
"stylelint-config-recommended-scss": "17.0.1",
Expand Down
6 changes: 3 additions & 3 deletions test/check-storage-cockpit
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ class TestStorageCockpitIntegration(VirtInstallMachineCase, StorageCase):
dev = "vda"
r.check_disk(dev, "16.1 GB vda (Virtio Block Device)")

r.check_disk_row(dev, "/", "vda3", "1.70 GB", False, None, False, 4)
r.check_disk_row(dev, "/", "vda3", "1.70 GB", False)
r.check_disk_mount_point_helper_text(dev, "/", "Needs at least")

i.check_next_disabled()
Expand Down Expand Up @@ -477,7 +477,7 @@ class TestStorageCockpitIntegration(VirtInstallMachineCase, StorageCase):

r.check_disk_row(dev, "/boot/efi", "vda1", "99.6 MB", False)
r.check_disk_row(dev, "/boot", "vda2", "1.07 GB", False)
r.check_disk_row(dev, "/", "vda3", "14.9 GB", False, None, False, 4)
r.check_disk_row(dev, "/", "vda3", "14.9 GB", False)

# Check fstab
fstab = m.execute("cat /etc/fstab")
Expand Down Expand Up @@ -541,7 +541,7 @@ class TestStorageCockpitIntegration(VirtInstallMachineCase, StorageCase):
r.check_disk(dev, "16.1 GB vda (Virtio Block Device)")

r.check_disk_row(dev, "/boot", "vda2", "1.07 GB", False)
r.check_disk_row(dev, "/", "vda3", "15.0 GB", False, None, False, 4)
r.check_disk_row(dev, "/", "vda3", "15.0 GB", False)
r.check_disk_row(dev, "/home", "vda3", "15.0 GB", False)

# Check fstab
Expand Down
10 changes: 6 additions & 4 deletions test/helpers/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

import os
import sys
from types import SimpleNamespace

from netlib import NetworkCase
from testlib import wait

HELPERS_DIR = os.path.dirname(__file__)
Expand Down Expand Up @@ -228,12 +230,12 @@ def preinstall_connection_test(self, installer, iface, configured=False):
])

def configure_iface_setting(self, setting_title):
b = self.browser
b.click(f"dt:contains('{setting_title}') + dd button")
shim = SimpleNamespace(browser=self.browser)
NetworkCase.configure_iface_setting(shim, setting_title)

def wait_for_iface_setting(self, setting_title, setting_value):
b = self.browser
b.wait_in_text(f"dt:contains('{setting_title}') + dd", setting_value)
shim = SimpleNamespace(browser=self.browser)
NetworkCase.wait_for_iface_setting(shim, setting_title, setting_value)

def set_mtu_on_iface(self, iface, mtu):
n = self
Expand Down
51 changes: 41 additions & 10 deletions test/helpers/review.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (C) 2022 Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later

import json
import os
import sys

Expand Down Expand Up @@ -60,18 +61,48 @@ def check_disk_row(
):
action = f"format as {fs_type}" if reformat else action or "mount"
encrypt_text = "encrypted" if is_encrypted and not reformat else "encrypt" if is_encrypted and reformat else ""
self.browser.wait_visible(
f"{prefix} table[aria-label={disk}] "
f"tbody{'' if rowIndex is None else f':nth-child({rowIndex})'} "
f"td:contains('{parent}') + "
f"td:contains('{size}') + "
f"td:contains('{action}') + "
f"td:contains('{encrypt_text}') + "
f"td:contains('{mount_point}')"
)
fragments = []
for part in (parent, str(size) if size != "" else "", action, encrypt_text, mount_point):
if part is not None and str(part) != "":
fragments.append(str(part))
Comment on lines +64 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for building the fragments list is redundant and contains a potential bug. The ternary expression str(size) if size != "" else "" will convert None to the string "None", which then passes the subsequent check and is added to the search fragments. Since the loop already handles string conversion and filters out empty values, this can be simplified into a list comprehension.

        fragments = [str(p) for p in (parent, size, action, encrypt_text, mount_point)
                     if p is not None and str(p) != ""]

table_selector = f"{prefix} #storage-review-table-{disk}".strip()
sel_json = json.dumps(table_selector)
frag_json = json.dumps(fragments)
if rowIndex is None:
cond = """(() => {
const table = document.querySelector(%s);
if (!table) return false;
const frags = %s;
const rows = table.querySelectorAll("tbody tr");
for (let i = 0; i < rows.length; i++) {
const t = rows[i].textContent;
if (frags.every(f => t.includes(f))) return true;
}
return false;
})()""" % (sel_json, frag_json)
else:
idx = int(rowIndex) - 1
cond = """(() => {
const table = document.querySelector(%s);
if (!table) return false;
const frags = %s;
const rows = table.querySelectorAll("tbody tr");
const tr = rows[%d];
if (!tr) return false;
const t = tr.textContent;
return frags.every(f => t.includes(f));
})()""" % (sel_json, frag_json, idx)
self.browser.wait_js_cond(cond)
Comment on lines +72 to +95
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new implementation using textContent.includes() is significantly less precise than the original Sizzle-based selector. The original code used the adjacent sibling combinator (+) to ensure that the expected values appeared in specific columns and in a specific order. The current JavaScript logic will match if the fragments appear anywhere in the row in any order, which increases the risk of false positives in tests. Consider implementing a check that validates the order of fragments within the row text or checks individual cells.


def check_disk_row_not_present(self, disk, mount):
self.browser.wait_not_present(f"table[aria-label={disk}] td:contains({mount})")
table_selector = f"#storage-review-table-{disk}".strip()
cond = """(() => {
const table = document.querySelector(%s);
if (!table) return true;
const needle = %s;
return !Array.from(table.querySelectorAll("tbody tr")).some(tr => tr.textContent.includes(needle));
})()""" % (json.dumps(table_selector), json.dumps(mount))
self.browser.wait_js_cond(cond)

def check_deleted_system(self, os_name):
self.browser.wait_in_text(f"#{self._step}-target-storage-note li", os_name)
Expand Down
169 changes: 129 additions & 40 deletions test/helpers/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,52 +516,139 @@ def set_scenario(self, scenario):


class StorageReclaimDialog():
_TABLE = "#reclaim-space-modal-table"

def __init__(self, browser):
self.browser = browser

def _reclaim_js_each_row(self, device, rowIndex, body):
"""Walk reclaim table rows. If rowIndex is set, it matches legacy CSS ``tbody:nth-child(rowIndex)``
(1-based index among the table's **direct** children), then ``tr`` containing ``device``.
If rowIndex is None, search all ``tbody tr``."""
tbl = json.dumps(self._TABLE)
dev = json.dumps(device)
nth_js = "null" if rowIndex is None else str(int(rowIndex))
return """(() => {
const table = document.querySelector(%s);
if (!table) return false;
const device = %s;
const tbodyNth = %s;
let rows;
if (tbodyNth === null) {
rows = table.querySelectorAll("tbody tr");
} else {
const child = table.children[tbodyNth - 1];
if (!child || child.tagName !== "TBODY") return false;
rows = child.querySelectorAll(":scope > tr");
}
for (let i = 0; i < rows.length; i++) {
const tr = rows[i];
if (!tr.textContent.includes(device)) continue;
%s
}
return false;
})()""" % (tbl, dev, nth_js, body)

def _reclaim_wait_row_button(self, device, aria_label, rowIndex=None, present=True, disabled=None):
"""disabled: None = any, True = must be disabled, False = must be enabled."""
aria = json.dumps(aria_label)
inner = """
const ariaLabel = %s;
const btn = Array.from(tr.querySelectorAll("button")).find(
b => b.getAttribute("aria-label") === ariaLabel
);
if (!btn) continue;
const dis = btn.disabled || btn.getAttribute("aria-disabled") === "true";
""" % aria
if disabled is True:
inner += """
if (!dis) continue;
return true;
"""
elif disabled is False:
inner += """
if (dis) continue;
return true;
"""
else:
inner += "return true;\n"
cond = self._reclaim_js_each_row(device, rowIndex, inner)
if present:
self.browser.wait_js_cond(cond)
else:
self.browser.wait_js_cond("!(" + cond + ")")

def _reclaim_click_row_button(self, device, aria_label, rowIndex=None):
aria = json.dumps(aria_label)
inner = """
const ariaLabel = %s;
const btn = Array.from(tr.querySelectorAll("button")).find(
b => b.getAttribute("aria-label") === ariaLabel
);
if (!btn || btn.disabled || btn.getAttribute("aria-disabled") === "true") continue;
btn.click();
return true;
""" % aria
script = self._reclaim_js_each_row(device, rowIndex, inner)
self.browser.wait_js_cond(script)

def reclaim_check_device_row(self, location, name=None, deviceType=None, space=None, locked=False):
self.browser.wait_visible(
"#reclaim-space-modal-table "
f"td[data-label=Location]:contains({location}) + " +
(f"td[data-label=Name]:contains({name}) + " if deviceType != "disk" else "") +
f"td[data-label=Type]:contains({deviceType}) + "
f"td[data-label=Space]:contains({space})"
)
loc = json.dumps(location or "")
nam = json.dumps("" if name is None else name)
dt = json.dumps(deviceType or "")
sp = json.dumps(space or "")
is_disk = "true" if deviceType == "disk" else "false"
cond = """(() => {
const table = document.querySelector(%s);
if (!table) return false;
const location = %s, name = %s, devType = %s, space = %s;
const isDisk = %s;
for (const tr of table.querySelectorAll("tbody tr")) {
const locEl = tr.querySelector('td[data-label="Location"]');
const nameEl = tr.querySelector('td[data-label="Name"]');
const typeEl = tr.querySelector('td[data-label="Type"]');
const spaceEl = tr.querySelector('td[data-label="Space"]');
if (!locEl || !typeEl || !spaceEl) continue;
if (!locEl.textContent.includes(location)) continue;
if (!isDisk && nameEl && !nameEl.textContent.includes(name)) continue;
if (!typeEl.textContent.includes(devType)) continue;
if (!spaceEl.textContent.includes(space)) continue;
return true;
}
return false;
})()""" % (json.dumps(self._TABLE), loc, nam, dt, sp, is_disk)
self.browser.wait_js_cond(cond)
if locked:
self.browser.wait_visible(
f"#reclaim-space-modal-table tr:contains({name}) "
f"td[data-label=Type] .reclaim-space-modal-device-locked"
)
name_sub = json.dumps(name or location or "")
lock_cond = """(() => {
const table = document.querySelector(%s);
if (!table) return false;
const nameNeedle = %s;
for (const tr of table.querySelectorAll("tbody tr")) {
if (!tr.textContent.includes(nameNeedle)) continue;
const typeEl = tr.querySelector('td[data-label="Type"]');
if (typeEl && typeEl.querySelector(".reclaim-space-modal-device-locked")) return true;
}
return false;
})()""" % (json.dumps(self._TABLE), name_sub)
self.browser.wait_js_cond(lock_cond)

def reclaim_remove_device(self, device):
self.browser.click(f"#reclaim-space-modal-table tr:contains('{device}') button[aria-label='delete']")
self._reclaim_click_row_button(device, "delete")

def reclaim_check_action_button_present(self, device, action, present=True, disabled=False):
if action == "shrink":
disabled = ":disabled" if disabled else ":not(:disabled)"
else:
disabled = "[aria-disabled='true']" if disabled else ":not([aria-disabled='true'])"
selector = (
"#reclaim-space-modal-table "
f"tr:contains('{device}') "
f"button[aria-label='{action}']{disabled}"
)

if present:
self.browser.wait_visible(selector)
else:
self.browser.wait_not_present(selector)
if not present:
self._reclaim_wait_row_button(device, action, present=False, disabled=None)
return
want_dis = True if disabled else False
self._reclaim_wait_row_button(device, action, present=True, disabled=want_dis)

def reclaim_modal_submit_and_check_warning(self, warning):
self.browser.click("button:contains('Reclaim space')")
self.browser.wait_in_text("#reclaim-space-modal .pf-v6-c-alert", warning)

def reclaim_shrink_device(self, device, new_size, current_size=None, rowIndex=None, ariaLabel="shrink"):
self.browser.click(
"#reclaim-space-modal-table "
f"tbody{'' if rowIndex is None else f':nth-child({rowIndex})'} "
f"tr:contains('{device}') button[aria-label='{ariaLabel}']"
)
self._reclaim_click_row_button(device, ariaLabel, rowIndex=rowIndex)
self.browser.wait_visible("#popover-reclaim-space-modal-shrink-body")
if current_size is not None:
self.browser.wait_val("#reclaim-space-modal-shrink-input", current_size)
Expand All @@ -577,19 +664,21 @@ def reclaim_shrink_device(self, device, new_size, current_size=None, rowIndex=No
self.reclaim_check_action_present(device, ariaLabel, rowIndex=rowIndex)

def reclaim_check_action_present(self, device, action, present=True, rowIndex=None):
selector = (
"#reclaim-space-modal-table "
f"tbody{'' if rowIndex is None else f':nth-child({rowIndex})'} "
f"tr:contains('{device}') "
"td:nth-child(5) " # Actions column
)
act = json.dumps(action)
inner = """
const actions = tr.querySelector("td:nth-child(5)");
if (!actions) continue;
const actionNeedle = %s;
if (actions.textContent.includes(actionNeedle)) return true;
""" % act
cond = self._reclaim_js_each_row(device, rowIndex, inner)
if present:
self.browser.wait_in_text(selector, action)
self.browser.wait_js_cond(cond)
else:
self.browser.wait_not_in_text(selector, action)
self.browser.wait_js_cond("!(" + cond + ")")

def reclaim_undo_action(self, device):
self.browser.click(f"#reclaim-space-modal-table tr:contains('{device}') button[aria-label='undo']")
self._reclaim_click_row_button(device, "undo")

def reclaim_check_available_space(self, space):
self.browser.wait_text("#reclaim-space-modal-hint-available-free-space", space)
Expand Down
Loading