From 2800ca5780dff89b3218080582e76b9cc8118c5f Mon Sep 17 00:00:00 2001 From: Pavel_Shilin Date: Mon, 9 Feb 2026 00:50:14 +0100 Subject: [PATCH 1/3] Add Selenium UI tests for language switcher, OS tabs, and code tabs --- manual/tests/test_code_tabs_sql_json.py | 260 ++++++++++++++++++++++ manual/tests/test_language_switcher.py | 95 ++++++++ manual/tests/test_os_tabs_installation.py | 155 +++++++++++++ 3 files changed, 510 insertions(+) create mode 100644 manual/tests/test_code_tabs_sql_json.py create mode 100644 manual/tests/test_language_switcher.py create mode 100644 manual/tests/test_os_tabs_installation.py diff --git a/manual/tests/test_code_tabs_sql_json.py b/manual/tests/test_code_tabs_sql_json.py new file mode 100644 index 0000000..a78598c --- /dev/null +++ b/manual/tests/test_code_tabs_sql_json.py @@ -0,0 +1,260 @@ +import pytest +import time +from selenium.webdriver.common.by import By +from core.base_test import BaseTest + + +@pytest.mark.usefixtures("setup_driver") +class TestCodeTabsSqlJson(BaseTest): + """Tests for SQL/HTTP/PHP code tab switching in documentation examples.""" + + PAGE_URL = "https://manual.manticoresearch.com/Quick_start_guide" + + def _find_code_tab_block_element(self, tab_names): + """Find a visible code lang-sel block containing all specified tab names. + + Returns the WebElement directly from JS to avoid index mismatch issues. + Excludes OS-related tab blocks. + """ + element = self.driver.execute_script(""" + var targetNames = arguments[0]; + var osKeywords = ['Ubuntu', 'Debian', 'Centos', 'RHEL', 'MacOS', + 'Windows', 'Docker', 'Kubernetes', 'Alma', 'Amazon', 'Oracle', 'Mint']; + var blocks = document.querySelectorAll('div.lang-sel'); + for (var i = 0; i < blocks.length; i++) { + var example = blocks[i].closest('.example'); + if (!example) continue; + if (getComputedStyle(example).display === 'none') continue; + // Get tab texts from ul.lang-tabs only (not from select) + var ul = blocks[i].querySelector('ul.lang-tabs'); + if (!ul) continue; + var lis = ul.querySelectorAll('li'); + var texts = []; + for (var j = 0; j < lis.length; j++) { + var span = lis[j].querySelector('span.lang-text'); + var t = span ? span.textContent.trim() : ''; + if (t) texts.push(t); + } + // Skip OS tab blocks + var isOsBlock = false; + for (var o = 0; o < osKeywords.length; o++) { + for (var t2 = 0; t2 < texts.length; t2++) { + if (texts[t2].indexOf(osKeywords[o]) !== -1) { + isOsBlock = true; break; + } + } + if (isOsBlock) break; + } + if (isOsBlock) continue; + // Check all target tabs exist + var allFound = true; + for (var k = 0; k < targetNames.length; k++) { + if (texts.indexOf(targetNames[k]) === -1) { + allFound = false; break; + } + } + if (allFound) return blocks[i]; + } + return null; + """, tab_names) + + if element is None: + pytest.fail(f"Code tab block with tabs {tab_names} not found") + return element + + def _get_visible_body_text(self, block): + """Get text of the currently visible example-body.""" + return self.driver.execute_script(""" + var example = arguments[0].closest('.example'); + if (!example) return ''; + var bodies = example.querySelectorAll('.example-body'); + for (var i = 0; i < bodies.length; i++) { + if (getComputedStyle(bodies[i]).display !== 'none') + return bodies[i].textContent.trim(); + } + return ''; + """, block) + + def _get_active_tab_text(self, block): + """Get the text of the currently active tab.""" + return self.driver.execute_script(""" + var ul = arguments[0].querySelector('ul.lang-tabs'); + if (!ul) return ''; + var active = ul.querySelector('li.active span.lang-text'); + return active ? active.textContent.trim() : ''; + """, block) + + def _click_tab(self, block, text): + """Click a tab by text via JS (works even if tab is hidden by CSS overflow).""" + clicked = self.driver.execute_script(""" + var block = arguments[0]; + var targetText = arguments[1]; + block.scrollIntoView({block: 'center'}); + var ul = block.querySelector('ul.lang-tabs'); + if (!ul) return false; + var lis = ul.querySelectorAll('li'); + for (var i = 0; i < lis.length; i++) { + var span = lis[i].querySelector('span.lang-text'); + if (span && span.textContent.trim() === targetText) { + lis[i].click(); + return true; + } + } + return false; + """, block, text) + if not clicked: + pytest.fail(f"Tab '{text}' not found in block") + time.sleep(2) + + def test_default_tab_is_sql(self): + """Verify that SQL is the default active tab in code examples.""" + self.driver.get(self.PAGE_URL) + time.sleep(2) + + block = self._find_code_tab_block_element(["SQL", "HTTP"]) + active = self._get_active_tab_text(block) + assert active == "SQL", f"Default active tab should be 'SQL', got: '{active}'" + + self.take_screenshot("code_tab_default") + + def test_switch_sql_to_http(self): + """Verify switching from SQL to HTTP tab changes code content.""" + self.driver.get(self.PAGE_URL) + time.sleep(2) + + block = self._find_code_tab_block_element(["SQL", "HTTP"]) + + # Ensure SQL is active + self._click_tab(block, "SQL") + sql_content = self._get_visible_body_text(block) + assert sql_content, "SQL tab should have visible content" + + # Switch to HTTP + self._click_tab(block, "HTTP") + + active = self._get_active_tab_text(block) + assert active == "HTTP", f"Active tab should be 'HTTP', got: '{active}'" + + http_content = self._get_visible_body_text(block) + assert http_content, "HTTP tab should have visible content" + assert http_content != sql_content, "HTTP content should differ from SQL content" + + self.take_screenshot("code_tab_http") + + def test_switch_to_php(self): + """Verify switching to PHP tab shows PHP code.""" + self.driver.get(self.PAGE_URL) + time.sleep(2) + + block = self._find_code_tab_block_element(["SQL", "PHP"]) + self._click_tab(block, "PHP") + + active = self._get_active_tab_text(block) + assert active == "PHP", f"Active tab should be 'PHP', got: '{active}'" + + content = self._get_visible_body_text(block) + assert "php" in content.lower() or "$" in content or "require" in content, \ + f"PHP tab should show PHP code, got: '{content[:100]}'" + + self.take_screenshot("code_tab_php") + + def test_switch_to_python(self): + """Verify switching to Python tab shows Python code.""" + self.driver.get(self.PAGE_URL) + time.sleep(2) + + block = self._find_code_tab_block_element(["SQL", "Python"]) + self._click_tab(block, "Python") + + active = self._get_active_tab_text(block) + assert active == "Python", f"Active tab should be 'Python', got: '{active}'" + + content = self._get_visible_body_text(block) + assert "import" in content or "manticoresearch" in content, \ + f"Python tab should show Python code, got: '{content[:100]}'" + + self.take_screenshot("code_tab_python") + + def test_tab_content_changes_on_switch(self): + """Verify that content actually changes when switching between tabs.""" + self.driver.get(self.PAGE_URL) + time.sleep(2) + + block = self._find_code_tab_block_element(["SQL", "HTTP"]) + + # Explicitly set to SQL first + self._click_tab(block, "SQL") + + # Re-fetch block after global tab sync (page may re-render) + block = self._find_code_tab_block_element(["SQL", "HTTP"]) + sql_content = self._get_visible_body_text(block) + + # Switch to HTTP + self._click_tab(block, "HTTP") + block = self._find_code_tab_block_element(["SQL", "HTTP"]) + http_content = self._get_visible_body_text(block) + + # Switch back to SQL + self._click_tab(block, "SQL") + block = self._find_code_tab_block_element(["SQL", "HTTP"]) + sql_again = self._get_visible_body_text(block) + + assert sql_content != http_content, \ + "SQL and HTTP content should be different" + assert sql_content == sql_again, \ + "SQL content should be the same after switching back" + + def test_global_tab_sync(self): + """Verify that switching a code tab syncs across all code blocks. + + The documentation site intentionally syncs tab selection globally + (e.g. switching to HTTP in one block switches all blocks to HTTP). + """ + self.driver.get(self.PAGE_URL) + time.sleep(2) + + # Find two code blocks that both have SQL and HTTP + elements = self.driver.execute_script(""" + var osKeywords = ['Ubuntu', 'Debian', 'Centos', 'RHEL', 'MacOS', + 'Windows', 'Docker', 'Kubernetes', 'Alma', 'Amazon', 'Oracle', 'Mint']; + var blocks = document.querySelectorAll('div.lang-sel'); + var found = []; + for (var i = 0; i < blocks.length; i++) { + var example = blocks[i].closest('.example'); + if (!example || getComputedStyle(example).display === 'none') continue; + var ul = blocks[i].querySelector('ul.lang-tabs'); + if (!ul) continue; + var lis = ul.querySelectorAll('li'); + var texts = []; + for (var j = 0; j < lis.length; j++) { + var span = lis[j].querySelector('span.lang-text'); + var t = span ? span.textContent.trim() : ''; + if (t) texts.push(t); + } + var isOs = texts.some(function(t) { + return osKeywords.some(function(kw) { return t.indexOf(kw) !== -1; }); + }); + if (isOs) continue; + if (texts.indexOf('SQL') !== -1 && texts.indexOf('HTTP') !== -1) + found.push(blocks[i]); + if (found.length >= 2) break; + } + return found; + """) + + if len(elements) < 2: + pytest.skip("Need at least 2 visible SQL/HTTP code blocks") + + block1, block2 = elements[0], elements[1] + + # Ensure both on SQL + self._click_tab(block1, "SQL") + assert self._get_active_tab_text(block1) == "SQL" + + # Switch block1 to HTTP + self._click_tab(block1, "HTTP") + + # Block2 should also switch to HTTP (global sync) + active2 = self._get_active_tab_text(block2) + assert active2 == "HTTP", \ + f"Tab sync: Block2 should also switch to HTTP, got: '{active2}'" diff --git a/manual/tests/test_language_switcher.py b/manual/tests/test_language_switcher.py new file mode 100644 index 0000000..3cda6f0 --- /dev/null +++ b/manual/tests/test_language_switcher.py @@ -0,0 +1,95 @@ +import pytest +import time +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import Select, WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from core.base_test import BaseTest + + +@pytest.mark.usefixtures("setup_driver") +class TestLanguageSwitcher(BaseTest): + """Tests for language switching functionality on the documentation site.""" + + BASE_URL = "https://manual.manticoresearch.com/" + + def test_switch_to_russian(self): + """Verify switching language to Russian changes content and URL.""" + self.driver.get(self.BASE_URL) + time.sleep(2) + + select = Select(self.driver.find_element(By.ID, "language-select")) + select.select_by_value("ru") + time.sleep(2) + + assert "/ru/" in self.driver.current_url, \ + f"URL should contain '/ru/', got: {self.driver.current_url}" + + h1 = self.driver.find_element(By.TAG_NAME, "h1") + assert h1.text == "Введение", \ + f"H1 should be 'Введение', got: '{h1.text}'" + + self.take_screenshot("language_russian") + + def test_switch_to_chinese(self): + """Verify switching language to Chinese changes content and URL.""" + self.driver.get(self.BASE_URL) + time.sleep(2) + + select = Select(self.driver.find_element(By.ID, "language-select")) + select.select_by_value("zh") + time.sleep(2) + + assert "/zh/" in self.driver.current_url, \ + f"URL should contain '/zh/', got: {self.driver.current_url}" + + self.take_screenshot("language_chinese") + + def test_switch_back_to_english(self): + """Verify switching from Russian back to English restores content.""" + self.driver.get(self.BASE_URL) + time.sleep(2) + + # Switch to Russian first + select = Select(self.driver.find_element(By.ID, "language-select")) + select.select_by_value("ru") + time.sleep(2) + + assert "/ru/" in self.driver.current_url + + # Switch back to English + select = Select(self.driver.find_element(By.ID, "language-select")) + select.select_by_value("") + time.sleep(2) + + assert "/ru/" not in self.driver.current_url, \ + f"URL should not contain '/ru/' after switching back, got: {self.driver.current_url}" + + h1 = self.driver.find_element(By.TAG_NAME, "h1") + assert h1.text == "Introduction", \ + f"H1 should be 'Introduction', got: '{h1.text}'" + + self.take_screenshot("language_back_to_english") + + def test_language_cookie_persists(self): + """Verify that language selection is saved in cookie.""" + self.driver.get(self.BASE_URL) + time.sleep(2) + + select = Select(self.driver.find_element(By.ID, "language-select")) + select.select_by_value("ru") + time.sleep(2) + + cookies = {c['name']: c['value'] for c in self.driver.get_cookies()} + assert cookies.get('selected-language') == 'ru', \ + f"Cookie 'selected-language' should be 'ru', got: {cookies.get('selected-language')}" + + # Navigate to another page — language should persist + self.driver.get("https://manual.manticoresearch.com/ru/Starting_the_server/Linux") + time.sleep(2) + + select_after = Select(self.driver.find_element(By.ID, "language-select")) + selected_option = select_after.first_selected_option + assert selected_option.get_attribute("value") == "ru", \ + f"Language selector should still be 'ru', got: '{selected_option.get_attribute('value')}'" + + self.take_screenshot("language_cookie_persists") diff --git a/manual/tests/test_os_tabs_installation.py b/manual/tests/test_os_tabs_installation.py new file mode 100644 index 0000000..c33ea6b --- /dev/null +++ b/manual/tests/test_os_tabs_installation.py @@ -0,0 +1,155 @@ +import pytest +import time +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from core.base_test import BaseTest + + +@pytest.mark.usefixtures("setup_driver") +class TestOsTabsInstallation(BaseTest): + """Tests for OS tab switching on the Installation page.""" + + INSTALL_URL = "https://manual.manticoresearch.com/Installation/Installation" + + def _get_tab_block(self): + """Find the OS tabs block on the Installation page.""" + self.driver.get(self.INSTALL_URL) + time.sleep(2) + + # Find the lang-sel block that contains OS tabs (RHEL, Debian, etc.) + blocks = self.driver.find_elements(By.CSS_SELECTOR, "div.lang-sel") + for block in blocks: + tabs = block.find_elements(By.CSS_SELECTOR, "li span.lang-text") + tab_texts = [t.get_attribute("textContent").strip() for t in tabs] + if any("RHEL" in t for t in tab_texts): + return block + pytest.fail("OS tabs block not found on Installation page") + + def _get_visible_body_text(self, block): + """Get text content of the currently visible example-body.""" + example = block.find_element(By.XPATH, "./ancestor::div[contains(@class, 'example')]") + bodies = example.find_elements(By.CSS_SELECTOR, ".example-body") + for body in bodies: + visible = self.driver.execute_script( + "return getComputedStyle(arguments[0]).display !== 'none'", body + ) + if visible: + return body.text.strip() + return "" + + def _get_active_tab_text(self, block): + """Get the text of the currently active tab.""" + active_li = block.find_element(By.CSS_SELECTOR, "li.active span.lang-text") + return active_li.get_attribute("textContent").strip() + + def _click_tab(self, block, tab_text): + """Click a tab by its text content.""" + tabs = block.find_elements(By.CSS_SELECTOR, "li") + for tab in tabs: + span = tab.find_element(By.CSS_SELECTOR, "span.lang-text") + if span.get_attribute("textContent").strip() == tab_text: + tab.click() + time.sleep(2) + return + pytest.fail(f"Tab '{tab_text}' not found") + + def test_default_tab_is_rhel(self): + """Verify that RHEL tab is active by default.""" + block = self._get_tab_block() + + active = self._get_active_tab_text(block) + assert "RHEL" in active, \ + f"Default active tab should contain 'RHEL', got: '{active}'" + + content = self._get_visible_body_text(block) + assert "yum install" in content, \ + f"RHEL tab should show yum install command, got: '{content[:100]}'" + + self.take_screenshot("os_tab_default_rhel") + + def test_switch_to_debian(self): + """Verify switching to Debian tab shows apt/wget commands.""" + block = self._get_tab_block() + + self._click_tab(block, "Debian, Ubuntu, Mint") + + active = self._get_active_tab_text(block) + assert "Debian" in active, \ + f"Active tab should contain 'Debian', got: '{active}'" + + content = self._get_visible_body_text(block) + assert "wget" in content or "apt" in content, \ + f"Debian tab should show wget/apt commands, got: '{content[:100]}'" + + self.take_screenshot("os_tab_debian") + + def test_switch_to_docker(self): + """Verify switching to Docker tab shows docker commands.""" + block = self._get_tab_block() + + self._click_tab(block, "Docker") + + active = self._get_active_tab_text(block) + assert "Docker" in active, \ + f"Active tab should be 'Docker', got: '{active}'" + + content = self._get_visible_body_text(block) + assert "docker" in content.lower(), \ + f"Docker tab should show docker commands, got: '{content[:100]}'" + + self.take_screenshot("os_tab_docker") + + def test_switch_to_macos(self): + """Verify switching to MacOS tab shows brew commands.""" + block = self._get_tab_block() + + self._click_tab(block, "MacOS") + + active = self._get_active_tab_text(block) + assert "MacOS" in active, \ + f"Active tab should be 'MacOS', got: '{active}'" + + content = self._get_visible_body_text(block) + assert "brew" in content, \ + f"MacOS tab should show brew command, got: '{content[:100]}'" + + self.take_screenshot("os_tab_macos") + + def test_switch_to_kubernetes(self): + """Verify switching to Kubernetes tab shows helm commands.""" + block = self._get_tab_block() + + self._click_tab(block, "Kubernetes") + + active = self._get_active_tab_text(block) + assert "Kubernetes" in active, \ + f"Active tab should be 'Kubernetes', got: '{active}'" + + content = self._get_visible_body_text(block) + assert "helm" in content, \ + f"Kubernetes tab should show helm commands, got: '{content[:100]}'" + + self.take_screenshot("os_tab_kubernetes") + + def test_switching_tabs_changes_content(self): + """Verify that switching between tabs actually changes visible content.""" + block = self._get_tab_block() + + # Explicitly select RHEL first + self._click_tab(block, "RHEL, Centos, Alma, Amazon, Oracle") + rhel_content = self._get_visible_body_text(block) + + # Switch to Docker + self._click_tab(block, "Docker") + docker_content = self._get_visible_body_text(block) + + assert rhel_content != docker_content, \ + "Content should change when switching between RHEL and Docker tabs" + + # Switch back to RHEL + self._click_tab(block, "RHEL, Centos, Alma, Amazon, Oracle") + rhel_again = self._get_visible_body_text(block) + + assert rhel_again == rhel_content, \ + "Content should be the same when switching back to RHEL" From 28ac99d7c9674c4617bcad76e319170bb737378b Mon Sep 17 00:00:00 2001 From: Pavel_Shilin Date: Mon, 9 Feb 2026 01:10:01 +0100 Subject: [PATCH 2/3] Trigger CI From 584e97a463b4985e4b3afef45ef41323ef45c191 Mon Sep 17 00:00:00 2001 From: Pavel_Shilin Date: Sun, 15 Feb 2026 19:37:14 +0100 Subject: [PATCH 3/3] Refactor Selenium tests: simplify helpers and stabilize Docker tab test --- manual/tests/test_code_tabs_sql_json.py | 252 ++++++---------------- manual/tests/test_language_switcher.py | 4 - manual/tests/test_os_tabs_installation.py | 20 +- 3 files changed, 80 insertions(+), 196 deletions(-) diff --git a/manual/tests/test_code_tabs_sql_json.py b/manual/tests/test_code_tabs_sql_json.py index a78598c..e3c2f77 100644 --- a/manual/tests/test_code_tabs_sql_json.py +++ b/manual/tests/test_code_tabs_sql_json.py @@ -3,6 +3,11 @@ from selenium.webdriver.common.by import By from core.base_test import BaseTest +OS_KEYWORDS = [ + 'Ubuntu', 'Debian', 'Centos', 'RHEL', 'MacOS', + 'Windows', 'Docker', 'Kubernetes', 'Alma', 'Amazon', 'Oracle', 'Mint', +] + @pytest.mark.usefixtures("setup_driver") class TestCodeTabsSqlJson(BaseTest): @@ -10,193 +15,108 @@ class TestCodeTabsSqlJson(BaseTest): PAGE_URL = "https://manual.manticoresearch.com/Quick_start_guide" - def _find_code_tab_block_element(self, tab_names): - """Find a visible code lang-sel block containing all specified tab names. + def _find_code_tab_blocks(self, tab_names): + """Find all visible code lang-sel blocks containing specified tab names. - Returns the WebElement directly from JS to avoid index mismatch issues. - Excludes OS-related tab blocks. + Returns a list of WebElements. Excludes OS-related tab blocks. """ - element = self.driver.execute_script(""" - var targetNames = arguments[0]; - var osKeywords = ['Ubuntu', 'Debian', 'Centos', 'RHEL', 'MacOS', - 'Windows', 'Docker', 'Kubernetes', 'Alma', 'Amazon', 'Oracle', 'Mint']; - var blocks = document.querySelectorAll('div.lang-sel'); - for (var i = 0; i < blocks.length; i++) { - var example = blocks[i].closest('.example'); - if (!example) continue; - if (getComputedStyle(example).display === 'none') continue; - // Get tab texts from ul.lang-tabs only (not from select) - var ul = blocks[i].querySelector('ul.lang-tabs'); - if (!ul) continue; - var lis = ul.querySelectorAll('li'); - var texts = []; - for (var j = 0; j < lis.length; j++) { - var span = lis[j].querySelector('span.lang-text'); - var t = span ? span.textContent.trim() : ''; - if (t) texts.push(t); - } - // Skip OS tab blocks - var isOsBlock = false; - for (var o = 0; o < osKeywords.length; o++) { - for (var t2 = 0; t2 < texts.length; t2++) { - if (texts[t2].indexOf(osKeywords[o]) !== -1) { - isOsBlock = true; break; - } - } - if (isOsBlock) break; - } - if (isOsBlock) continue; - // Check all target tabs exist - var allFound = true; - for (var k = 0; k < targetNames.length; k++) { - if (texts.indexOf(targetNames[k]) === -1) { - allFound = false; break; - } - } - if (allFound) return blocks[i]; - } - return null; - """, tab_names) - - if element is None: + blocks = self.driver.find_elements(By.CSS_SELECTOR, "div.lang-sel") + result = [] + for block in blocks: + example = block.find_element( + By.XPATH, "./ancestor::div[contains(@class, 'example')]" + ) + if not example.is_displayed(): + continue + tabs = block.find_elements( + By.CSS_SELECTOR, "ul.lang-tabs li span.lang-text" + ) + tab_texts = [t.get_attribute("textContent").strip() for t in tabs] + if any(kw in text for kw in OS_KEYWORDS for text in tab_texts): + continue + if all(name in tab_texts for name in tab_names): + result.append(block) + return result + + def _find_code_tab_block(self, tab_names): + """Find the first visible code lang-sel block with specified tabs.""" + blocks = self._find_code_tab_blocks(tab_names) + if not blocks: pytest.fail(f"Code tab block with tabs {tab_names} not found") - return element + return blocks[0] def _get_visible_body_text(self, block): """Get text of the currently visible example-body.""" - return self.driver.execute_script(""" - var example = arguments[0].closest('.example'); - if (!example) return ''; - var bodies = example.querySelectorAll('.example-body'); - for (var i = 0; i < bodies.length; i++) { - if (getComputedStyle(bodies[i]).display !== 'none') - return bodies[i].textContent.trim(); - } - return ''; - """, block) + example = block.find_element( + By.XPATH, "./ancestor::div[contains(@class, 'example')]" + ) + bodies = example.find_elements(By.CSS_SELECTOR, ".example-body") + for body in bodies: + if body.is_displayed(): + return body.text.strip() + return "" def _get_active_tab_text(self, block): """Get the text of the currently active tab.""" - return self.driver.execute_script(""" - var ul = arguments[0].querySelector('ul.lang-tabs'); - if (!ul) return ''; - var active = ul.querySelector('li.active span.lang-text'); - return active ? active.textContent.trim() : ''; - """, block) + active = block.find_element( + By.CSS_SELECTOR, "li.active span.lang-text" + ) + return active.get_attribute("textContent").strip() def _click_tab(self, block, text): - """Click a tab by text via JS (works even if tab is hidden by CSS overflow).""" - clicked = self.driver.execute_script(""" - var block = arguments[0]; - var targetText = arguments[1]; - block.scrollIntoView({block: 'center'}); - var ul = block.querySelector('ul.lang-tabs'); - if (!ul) return false; - var lis = ul.querySelectorAll('li'); - for (var i = 0; i < lis.length; i++) { - var span = lis[i].querySelector('span.lang-text'); - if (span && span.textContent.trim() === targetText) { - lis[i].click(); - return true; - } - } - return false; - """, block, text) - if not clicked: - pytest.fail(f"Tab '{text}' not found in block") - time.sleep(2) + """Click a tab by its text content.""" + tabs = block.find_elements(By.CSS_SELECTOR, "ul.lang-tabs li") + for tab in tabs: + span = tab.find_element(By.CSS_SELECTOR, "span.lang-text") + if span.get_attribute("textContent").strip() == text: + tab.click() + time.sleep(2) + return + pytest.fail(f"Tab '{text}' not found in block") def test_default_tab_is_sql(self): """Verify that SQL is the default active tab in code examples.""" self.driver.get(self.PAGE_URL) time.sleep(2) - block = self._find_code_tab_block_element(["SQL", "HTTP"]) + block = self._find_code_tab_block(["SQL", "HTTP"]) active = self._get_active_tab_text(block) assert active == "SQL", f"Default active tab should be 'SQL', got: '{active}'" - self.take_screenshot("code_tab_default") - - def test_switch_sql_to_http(self): - """Verify switching from SQL to HTTP tab changes code content.""" + @pytest.mark.parametrize("tab_name", ["HTTP", "PHP", "Python"]) + def test_switch_to_tab(self, tab_name): + """Verify switching to a specific tab activates it and shows different content.""" self.driver.get(self.PAGE_URL) time.sleep(2) - block = self._find_code_tab_block_element(["SQL", "HTTP"]) + block = self._find_code_tab_block(["SQL", tab_name]) - # Ensure SQL is active self._click_tab(block, "SQL") sql_content = self._get_visible_body_text(block) - assert sql_content, "SQL tab should have visible content" - - # Switch to HTTP - self._click_tab(block, "HTTP") - - active = self._get_active_tab_text(block) - assert active == "HTTP", f"Active tab should be 'HTTP', got: '{active}'" - - http_content = self._get_visible_body_text(block) - assert http_content, "HTTP tab should have visible content" - assert http_content != sql_content, "HTTP content should differ from SQL content" - - self.take_screenshot("code_tab_http") - - def test_switch_to_php(self): - """Verify switching to PHP tab shows PHP code.""" - self.driver.get(self.PAGE_URL) - time.sleep(2) - block = self._find_code_tab_block_element(["SQL", "PHP"]) - self._click_tab(block, "PHP") + self._click_tab(block, tab_name) active = self._get_active_tab_text(block) - assert active == "PHP", f"Active tab should be 'PHP', got: '{active}'" + assert active == tab_name, f"Active tab should be '{tab_name}', got: '{active}'" content = self._get_visible_body_text(block) - assert "php" in content.lower() or "$" in content or "require" in content, \ - f"PHP tab should show PHP code, got: '{content[:100]}'" - - self.take_screenshot("code_tab_php") - - def test_switch_to_python(self): - """Verify switching to Python tab shows Python code.""" - self.driver.get(self.PAGE_URL) - time.sleep(2) - - block = self._find_code_tab_block_element(["SQL", "Python"]) - self._click_tab(block, "Python") - - active = self._get_active_tab_text(block) - assert active == "Python", f"Active tab should be 'Python', got: '{active}'" - - content = self._get_visible_body_text(block) - assert "import" in content or "manticoresearch" in content, \ - f"Python tab should show Python code, got: '{content[:100]}'" - - self.take_screenshot("code_tab_python") + assert content, f"{tab_name} tab should have visible content" + assert content != sql_content, f"{tab_name} content should differ from SQL content" def test_tab_content_changes_on_switch(self): - """Verify that content actually changes when switching between tabs.""" + """Verify that content changes when switching tabs and restores when switching back.""" self.driver.get(self.PAGE_URL) time.sleep(2) - block = self._find_code_tab_block_element(["SQL", "HTTP"]) + block = self._find_code_tab_block(["SQL", "HTTP"]) - # Explicitly set to SQL first self._click_tab(block, "SQL") - - # Re-fetch block after global tab sync (page may re-render) - block = self._find_code_tab_block_element(["SQL", "HTTP"]) sql_content = self._get_visible_body_text(block) - # Switch to HTTP self._click_tab(block, "HTTP") - block = self._find_code_tab_block_element(["SQL", "HTTP"]) http_content = self._get_visible_body_text(block) - # Switch back to SQL self._click_tab(block, "SQL") - block = self._find_code_tab_block_element(["SQL", "HTTP"]) sql_again = self._get_visible_body_text(block) assert sql_content != http_content, \ @@ -205,56 +125,26 @@ def test_tab_content_changes_on_switch(self): "SQL content should be the same after switching back" def test_global_tab_sync(self): - """Verify that switching a code tab syncs across all code blocks. + """Verify that switching a code tab in one block syncs all code blocks. - The documentation site intentionally syncs tab selection globally - (e.g. switching to HTTP in one block switches all blocks to HTTP). + The documentation site syncs tab selection globally — switching to HTTP + in one block should switch all blocks to HTTP. """ self.driver.get(self.PAGE_URL) time.sleep(2) - # Find two code blocks that both have SQL and HTTP - elements = self.driver.execute_script(""" - var osKeywords = ['Ubuntu', 'Debian', 'Centos', 'RHEL', 'MacOS', - 'Windows', 'Docker', 'Kubernetes', 'Alma', 'Amazon', 'Oracle', 'Mint']; - var blocks = document.querySelectorAll('div.lang-sel'); - var found = []; - for (var i = 0; i < blocks.length; i++) { - var example = blocks[i].closest('.example'); - if (!example || getComputedStyle(example).display === 'none') continue; - var ul = blocks[i].querySelector('ul.lang-tabs'); - if (!ul) continue; - var lis = ul.querySelectorAll('li'); - var texts = []; - for (var j = 0; j < lis.length; j++) { - var span = lis[j].querySelector('span.lang-text'); - var t = span ? span.textContent.trim() : ''; - if (t) texts.push(t); - } - var isOs = texts.some(function(t) { - return osKeywords.some(function(kw) { return t.indexOf(kw) !== -1; }); - }); - if (isOs) continue; - if (texts.indexOf('SQL') !== -1 && texts.indexOf('HTTP') !== -1) - found.push(blocks[i]); - if (found.length >= 2) break; - } - return found; - """) - - if len(elements) < 2: + code_blocks = self._find_code_tab_blocks(["SQL", "HTTP"]) + if len(code_blocks) < 2: pytest.skip("Need at least 2 visible SQL/HTTP code blocks") - block1, block2 = elements[0], elements[1] + block1, block2 = code_blocks[0], code_blocks[1] - # Ensure both on SQL self._click_tab(block1, "SQL") assert self._get_active_tab_text(block1) == "SQL" - # Switch block1 to HTTP + # Switch block1 to HTTP — block2 should sync automatically self._click_tab(block1, "HTTP") - # Block2 should also switch to HTTP (global sync) active2 = self._get_active_tab_text(block2) assert active2 == "HTTP", \ - f"Tab sync: Block2 should also switch to HTTP, got: '{active2}'" + f"Tab sync: block2 should also switch to HTTP, got: '{active2}'" diff --git a/manual/tests/test_language_switcher.py b/manual/tests/test_language_switcher.py index 3cda6f0..3cf0dc2 100644 --- a/manual/tests/test_language_switcher.py +++ b/manual/tests/test_language_switcher.py @@ -28,7 +28,6 @@ def test_switch_to_russian(self): assert h1.text == "Введение", \ f"H1 should be 'Введение', got: '{h1.text}'" - self.take_screenshot("language_russian") def test_switch_to_chinese(self): """Verify switching language to Chinese changes content and URL.""" @@ -42,7 +41,6 @@ def test_switch_to_chinese(self): assert "/zh/" in self.driver.current_url, \ f"URL should contain '/zh/', got: {self.driver.current_url}" - self.take_screenshot("language_chinese") def test_switch_back_to_english(self): """Verify switching from Russian back to English restores content.""" @@ -68,7 +66,6 @@ def test_switch_back_to_english(self): assert h1.text == "Introduction", \ f"H1 should be 'Introduction', got: '{h1.text}'" - self.take_screenshot("language_back_to_english") def test_language_cookie_persists(self): """Verify that language selection is saved in cookie.""" @@ -92,4 +89,3 @@ def test_language_cookie_persists(self): assert selected_option.get_attribute("value") == "ru", \ f"Language selector should still be 'ru', got: '{selected_option.get_attribute('value')}'" - self.take_screenshot("language_cookie_persists") diff --git a/manual/tests/test_os_tabs_installation.py b/manual/tests/test_os_tabs_installation.py index c33ea6b..8fbc6a0 100644 --- a/manual/tests/test_os_tabs_installation.py +++ b/manual/tests/test_os_tabs_installation.py @@ -31,10 +31,7 @@ def _get_visible_body_text(self, block): example = block.find_element(By.XPATH, "./ancestor::div[contains(@class, 'example')]") bodies = example.find_elements(By.CSS_SELECTOR, ".example-body") for body in bodies: - visible = self.driver.execute_script( - "return getComputedStyle(arguments[0]).display !== 'none'", body - ) - if visible: + if body.is_displayed(): return body.text.strip() return "" @@ -45,12 +42,18 @@ def _get_active_tab_text(self, block): def _click_tab(self, block, tab_text): """Click a tab by its text content.""" + old_content = self._get_visible_body_text(block) tabs = block.find_elements(By.CSS_SELECTOR, "li") for tab in tabs: span = tab.find_element(By.CSS_SELECTOR, "span.lang-text") if span.get_attribute("textContent").strip() == tab_text: - tab.click() - time.sleep(2) + self.driver.execute_script("arguments[0].click();", tab) + # Wait for content to change (up to 5 seconds) + for _ in range(10): + time.sleep(0.5) + new_content = self._get_visible_body_text(block) + if new_content != old_content: + break return pytest.fail(f"Tab '{tab_text}' not found") @@ -66,7 +69,6 @@ def test_default_tab_is_rhel(self): assert "yum install" in content, \ f"RHEL tab should show yum install command, got: '{content[:100]}'" - self.take_screenshot("os_tab_default_rhel") def test_switch_to_debian(self): """Verify switching to Debian tab shows apt/wget commands.""" @@ -82,7 +84,6 @@ def test_switch_to_debian(self): assert "wget" in content or "apt" in content, \ f"Debian tab should show wget/apt commands, got: '{content[:100]}'" - self.take_screenshot("os_tab_debian") def test_switch_to_docker(self): """Verify switching to Docker tab shows docker commands.""" @@ -98,7 +99,6 @@ def test_switch_to_docker(self): assert "docker" in content.lower(), \ f"Docker tab should show docker commands, got: '{content[:100]}'" - self.take_screenshot("os_tab_docker") def test_switch_to_macos(self): """Verify switching to MacOS tab shows brew commands.""" @@ -114,7 +114,6 @@ def test_switch_to_macos(self): assert "brew" in content, \ f"MacOS tab should show brew command, got: '{content[:100]}'" - self.take_screenshot("os_tab_macos") def test_switch_to_kubernetes(self): """Verify switching to Kubernetes tab shows helm commands.""" @@ -130,7 +129,6 @@ def test_switch_to_kubernetes(self): assert "helm" in content, \ f"Kubernetes tab should show helm commands, got: '{content[:100]}'" - self.take_screenshot("os_tab_kubernetes") def test_switching_tabs_changes_content(self): """Verify that switching between tabs actually changes visible content."""