diff --git a/test/ui/fixtures/ui_fixtures.py b/test/ui/fixtures/ui_fixtures.py index 52eb1784b3..dd4f385ed7 100644 --- a/test/ui/fixtures/ui_fixtures.py +++ b/test/ui/fixtures/ui_fixtures.py @@ -49,7 +49,7 @@ def is_container_healthy() -> bool: return result.returncode == 0 -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def browser_setup( request: pytest.FixtureRequest, ) -> Generator[tuple[WebDriver, str], None, None]: diff --git a/test/ui/test_80_lightspeed.py b/test/ui/test_80_lightspeed.py index c0f468b13b..8f0af9a649 100644 --- a/test/ui/test_80_lightspeed.py +++ b/test/ui/test_80_lightspeed.py @@ -12,7 +12,7 @@ vscode_playbook_generation, vscode_prediction, vscode_role_generation, - vscode_run_command, + vscode_run_command_f1, wait_displayed, ) @@ -30,55 +30,19 @@ tasks: - name: install dnsutils""" -MULTI_SUGGESTIONS_PLAYBOOK = """--- -- name: Playbook - hosts: all -tasks: - - name: Install dnsutils""" logged_in_flag = False def vscode_login_wrapper(driver: Any) -> None: """Log in with VSCode at the scope of this file.""" - vscode_login(driver) + vscode_login(driver, device_login=True) global logged_in_flag # noqa: PLW0603 logged_in_flag = True -# @pytest.mark.flaky(reruns=6, reruns_delay=10) -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") -def test_vscode_widget( - browser_setup: Any, - screenshot_on_fail: Any, - close_editors: Any, -) -> None: - """Test that the vs-code widget shows up and works correctly. - - Tests widget behavior when multiple suggestion extensions are installed. - """ - driver, _ = browser_setup - - vscode_login_wrapper(driver) - vscode_prediction( - driver, - "playbook.yaml", - MULTI_SUGGESTIONS_PLAYBOOK, - accept=False, - mutil_provider=True, - ) - # validate the widget is working correctly - wait_displayed( - driver, - "//span[contains(normalize-space(.), 'pilot')]", - timeout=20, - ) - - -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_vscode_playbook_explanation( browser_setup: Any, screenshot_on_fail: Any, - close_editors: Any, ) -> None: """Test the playbook explanation feature from VSCode.""" driver, _ = browser_setup @@ -105,11 +69,9 @@ def test_vscode_playbook_explanation( assert phrase in explanation, msg -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_vscode_playbook_generation( browser_setup: Any, screenshot_on_fail: Any, - close_editors: Any, ) -> None: """Test the playbook generation feature from VSCode.""" driver, _ = browser_setup @@ -127,11 +89,9 @@ def test_vscode_playbook_generation( assert all(txt in playbook for txt in expected_playbook), "Error- bad playbook" -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_vscode_role_generation( browser_setup: Any, screenshot_on_fail: Any, - close_editors: Any, ) -> None: """Test the role generation feature from VSCode.""" driver, _ = browser_setup @@ -144,11 +104,9 @@ def test_vscode_role_generation( assert "ansible.builtin.package" in tasks -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_vscode_lightspeed_explorer( browser_setup: Any, screenshot_on_fail: Any, - close_editors: Any, ) -> None: """Test the Lightspeed explorer view from VSCode.""" driver, _ = browser_setup @@ -156,27 +114,28 @@ def test_vscode_lightspeed_explorer( if not logged_in_flag: vscode_login_wrapper(driver) - vscode_run_command(driver, ">Ansible: Focus on Ansible Lightspeed View") - find_element_across_iframes( + vscode_run_command_f1(driver, "Ansible: Focus on Ansible Lightspeed View") + # verify logged-in state via the Accounts menu + accounts_button = wait_displayed(driver, "//a[@aria-label='Accounts']") + accounts_button.click() + wait_displayed( driver, - # After https://github.com/tomershinhar/selenium-vscode-container/pull/16 - # is deployed - # "//p[@class='user-content' and contains(normalize-space(.), 'Logged in as:')]", - "//*[contains(normalize-space(.), 'Logged in as:')]", + "//*[contains(normalize-space(.), 'lightspeed-test-user')]", + timeout=5, ) find_element_across_iframes( driver, - "//vscode-button[contains(normalize-space(.), 'Generate a playbook')]", + "//*[contains(normalize-space(.), 'Generate Playbook')]", ) find_element_across_iframes( driver, - "//vscode-button[contains(normalize-space(.), 'Explain the current playbook')]", + "//*[contains(normalize-space(.), 'Explain Playbook')]", ) find_element_across_iframes( driver, - "//vscode-button[contains(normalize-space(.), 'Generate a role')]", + "//*[contains(normalize-space(.), 'Generate Role')]", ) find_element_across_iframes( driver, - "//vscode-button[contains(normalize-space(.), 'Explain the current role')]", + "//*[contains(normalize-space(.), 'Explain Role')]", ) diff --git a/test/ui/test_81_login.py b/test/ui/test_81_login.py index 7e37f23687..e7079d6806 100644 --- a/test/ui/test_81_login.py +++ b/test/ui/test_81_login.py @@ -30,233 +30,7 @@ tasks: - name: install dnsutils""" -MULTI_SUGGESTIONS_PLAYBOOK = """--- -- name: Playbook - hosts: all -tasks: - - name: Install dnsutils""" - -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") -def test_unsubed_login( - browser_setup: Any, - lightspeed_logout_teardown: Any, - screenshot_on_fail: Any, - close_editors: Any, -) -> None: - """Test the login page for a user without subscription.""" - driver, login_url = browser_setup - # here we should not be logged in - driver.get(login_url) - title = wait_displayed(driver, "//h1", timeout=10) - assert ( - title.text - == "Log in to Red Hat Ansible Lightspeed with IBM watsonx Code Assistant" - ) - # now we log in validate we still can't get to the admin portal - redhat_button = wait_displayed( - driver, - "//a[normalize-space(.)='Log in with Red Hat']", - ) - redhat_button.click() - # since we logged out, we need to go through RHSSO again - assert "redhat.com/auth" in driver.current_url - username = sso_auth_flow(driver, no_sub=True) - # set logout as teardown - lightspeed_logout_teardown(driver) - wait_displayed(driver, f"//p[contains(normalize-space(.), '{username}')]") - title = driver.find_element(by="xpath", value="//h1") - assert ( - title.text == "Your organization doesn't have access to " - "Red Hat Ansible Lightspeed with IBM watsonx Code Assistant." - ) - body = driver.find_elements(by="xpath", value="//p") - i = 0 - assert ( - body[i].text == "Your organization doesn't have access to " - "Red Hat Ansible Lightspeed with IBM watsonx Code Assistant." - ) - assert ( - body[i + 1].text - == "Contact your Red Hat Organization's administrator for more information." - ) - assert body[i + 2].text == username - - -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") -def test_unsubed_admin_login( - browser_setup: Any, - lightspeed_logout_teardown: Any, - screenshot_on_fail: Any, - close_editors: Any, -) -> None: - """Test the login page for a user without subscription.""" - driver, login_url = browser_setup - # here we should not be logged in - driver.get(login_url) - title = wait_displayed(driver, "//h1", timeout=10) - assert ( - title.text - == "Log in to Red Hat Ansible Lightspeed with IBM watsonx Code Assistant" - ) - # now we log in validate we still can't get to the admin portal - redhat_button = wait_displayed( - driver, - "//a[normalize-space(.)='Log in with Red Hat']", - ) - redhat_button.click() - # since we logged out, we need to go through RHSSO again - assert "redhat.com/auth" in driver.current_url - username = sso_auth_flow(driver, no_sub=True, admin_login=True) - # set logout as teardown - lightspeed_logout_teardown(driver) - wait_displayed(driver, f"//p[contains(normalize-space(.), '{username}')]") - title = driver.find_element(by="xpath", value="//h1") - assert ( - title.text - == "Your organization doesn't have access to Red Hat Ansible Lightspeed " - "with IBM watsonx Code Assistant." - ) - body = driver.find_elements(by="xpath", value="//p") - i = 0 - assert ( - body[i].text - == "Your organization doesn't have access to Red Hat Ansible Lightspeed with " - "IBM watsonx Code Assistant." - ) - assert ( - body[i + 1].text == "You do not have an Active subscription to Ansible " - "Automation Platform which is required to use Red Hat Ansible Lightspeed " - "with IBM watsonx Code Assistant." - ) - assert body[i + 2].text == username - assert body[i + 3].text == "Role: administrator" - - -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") -def test_no_wca_user_login( - browser_setup: Any, - lightspeed_logout_teardown: Any, - screenshot_on_fail: Any, - close_editors: Any, -) -> None: - """Test the login page for a user with subscribed org that does not have wca set-up.""" - driver, login_url = browser_setup - # here we should not be logged in - driver.get(login_url) - title = wait_displayed(driver, "//h1", timeout=10) - assert ( - title.text - == "Log in to Red Hat Ansible Lightspeed with IBM watsonx Code Assistant" - ) - # now we log in validate we still can't get to the admin portal - redhat_button = wait_displayed( - driver, - "//a[normalize-space(.)='Log in with Red Hat']", - ) - redhat_button.click() - # since we logged out, we need to go through RHSSO again - assert "redhat.com/auth" in driver.current_url - username = sso_auth_flow(driver, no_wca=True) - # set logout as teardown - lightspeed_logout_teardown(driver) - title = driver.find_element(by="xpath", value="//h1") - body = driver.find_elements(by="xpath", value="//p") - # wait for all required redirects happens before continue - time.sleep(5) - if driver.current_url.endswith("/trial/"): - # Trial case - assert title.text.startswith( - "Red Hat Ansible Lightspeed with IBM watsonx Code Assistant", - ) - assert any( - "Start a trial to Ansible Lightspeed with IBM watsonx Code Assistant" - in i.text - for i in body - ) - else: - wait_displayed(driver, f"//p[contains(normalize-space(.), '{username}')]") - assert ( - title.text == "You are a licensed Red Hat Ansible Lightspeed with IBM " - "watsonx Code Assistant user but your " - "administrator has not configured the service for your organization.\n" - "Contact your organization administrator to have them complete Red Hat " - "Ansible Lightspeed with IBM watsonx Code Assistant configuration." - ) - i = 0 - assert ( - body[i].text - == "You are a licensed Red Hat Ansible Lightspeed with IBM watsonx Code Assistant " - "user but your administrator has not configured the service for your organization." - ) - assert ( - body[i + 1].text == "Contact your organization administrator to have them " - "complete Red Hat Ansible Lightspeed " - "with IBM watsonx Code Assistant configuration." - ) - assert ( - body[i + 2].text - == "Contact your Red Hat Organization's administrator for more information." - ) - assert body[i + 3].text == username - assert body[i + 4].text == "Role: licensed user" - - -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") -def test_no_wca_admin_login( - browser_setup: Any, - lightspeed_logout_teardown: Any, - screenshot_on_fail: Any, -) -> None: - """Test the login page for an admin user with subscribed org that does not have wca set-up.""" - driver, login_url = browser_setup - # here we should not be logged in - driver.get(login_url) - title = wait_displayed(driver, "//h1", timeout=10) - assert ( - title.text - == "Log in to Red Hat Ansible Lightspeed with IBM watsonx Code Assistant" - ) - # now we log in validate we still can't get to the admin portal - redhat_button = wait_displayed( - driver, - "//a[normalize-space(.)='Log in with Red Hat']", - ) - redhat_button.click() - # since we logged out, we need to go through RHSSO again - assert "redhat.com/auth" in driver.current_url - username = sso_auth_flow(driver, no_wca=True, admin_login=True) - # set logout as teardown - lightspeed_logout_teardown(driver) - wait_displayed(driver, f"//p[contains(normalize-space(.), '{username}')]") - title = driver.find_element(by="xpath", value="//h1") - assert title.text == "Red Hat Ansible Lightspeed with IBM watsonx Code Assistant" - body = driver.find_elements(by="xpath", value="//p") - if driver.current_url.endswith("/trial/"): - # Trial case - assert title.text.startswith( - "Red Hat Ansible Lightspeed with IBM watsonx Code Assistant", - ) - assert any( - "This will only apply to you and will not affect your organization." - in i.text - for i in body - ) - else: - i = 0 - assert ( - body[i].text == "You are a Red Hat organization administrator for " - "Red Hat Ansible Lightspeed with IBM watsonx Code Assistant." - " IBM watsonx Code Assistant" - " model settings have not been configured for your organization. Click here " - "to access the Red Hat Ansible Lightspeed with IBM watsonx Code Assistant " - "admin portal to complete configuration." - ) - assert body[i + 1].text == username - assert body[i + 2].text == "Role: administrator, licensed user" - - -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_login_page( browser_setup: Any, screenshot_on_fail: Any, @@ -300,7 +74,6 @@ def test_login_page( assert button.is_enabled() -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_sso_auth_flow( browser_setup: Any, screenshot_on_fail: Any, @@ -330,14 +103,13 @@ def test_sso_auth_flow( rh_button = click_and_wait( driver, login_button, - "//a[normalize-space(.)='Log in with Ansible Automation Platform']", + "//a[normalize-space(.)='Log in with Red Hat']", ) assert rh_button is not None assert rh_button.is_displayed() assert rh_button.is_enabled() -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_admin_portal_error( browser_setup: Any, screenshot_on_fail: Any, @@ -382,7 +154,6 @@ def test_admin_portal_error( admin_portal_logout(driver) -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") def test_vscode_rhsso_auth_flow( browser_setup: Any, screenshot_on_fail: Any, @@ -419,5 +190,3 @@ def test_vscode_rhsso_auth_flow( required_fields = ["url", "path", "source", "license", "score"] for attrib in content_match.values(): assert all(attrib[field] for field in required_fields) - # playbook = get_vscode_file_text(driver) - # assert all(text in playbook for text in ["ansible.builtin.package", "name: dnsutils"]) diff --git a/test/ui/test_82_lightspeed_trial.py b/test/ui/test_82_lightspeed_trial.py deleted file mode 100644 index 73dbfe3071..0000000000 --- a/test/ui/test_82_lightspeed_trial.py +++ /dev/null @@ -1,37 +0,0 @@ -"""This module is for testing the Ansible Lightspeed Trial functionality.""" - -# pylint: disable=E0401, W0613, R0801, W0603 -from typing import Any - -import pytest - -from test.ui.utils.ui_utils import vscode_login, vscode_trial_button - -pytestmark = pytest.mark.lightspeed - - -PLAYBOOK_CONTENT = """ ---- -- name: example - hosts: all -become: true - -tasks: -- name: install dnsutils""" - - -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") -@pytest.mark.vscode_trial -def test_vscode_trial_button( - new_browser: Any, - lightspeed_logout_teardown: Any, - screenshot_on_fail: Any, - close_editors: Any, -) -> None: - """Test the playbook explanation feature from vs-code.""" - # We use a function scoped browser because the connection is different - driver, _, _ = new_browser - - vscode_login(driver, no_wca=True) - # Ensure we get the Trial button - assert vscode_trial_button(driver, "playbook.yaml", PLAYBOOK_CONTENT) diff --git a/test/ui/utils/ui_utils.py b/test/ui/utils/ui_utils.py index d72121b050..17f25f82f6 100644 --- a/test/ui/utils/ui_utils.py +++ b/test/ui/utils/ui_utils.py @@ -5,9 +5,7 @@ import os import time from collections.abc import Generator -from typing import Any -import pytest from selenium.common import ( ElementClickInterceptedException, NoSuchElementException, @@ -30,7 +28,6 @@ LIGHTSPEED_USER = os.environ.get("LIGHTSPEED_USER", "") LIGHTSPEED_PASSWORD = os.environ.get("LIGHTSPEED_PASSWORD", "") - # Move cursor to the "Explain the playbook with Ansible Lightspeed" menu item. # Note: The required number of DOWN key presses varies by VSCode version. POSITION_OF_ANSIBLE_EXPLAIN = 9 @@ -185,30 +182,19 @@ def user_is_auth(driver: WebDriver) -> bool: return bool(elts) -@pytest.mark.skip(reason="See https://redhat.atlassian.net/browse/AAP-67210") -def sso_auth_flow( # noqa: PLR0913 +def sso_auth_flow( driver: WebDriver, username: str = LIGHTSPEED_USER, password: str = LIGHTSPEED_PASSWORD, - *, - admin_login: bool = False, - no_wca: bool = False, - no_sub: bool = False, ) -> str: """Perform all the steps to log in with Red Hat SSO. - We have many parameters because of tests in test_login. - Args: driver: WebDriver instance username: Username for authentication password: Password for authentication - admin_login: Whether this is an admin login - no_wca: Whether to skip WCA - no_sub: Whether to skip subscription """ - user = LIGHTSPEED_USER - password = LIGHTSPEED_PASSWORD + user = username assert user assert password @@ -301,6 +287,11 @@ def redhat_logout(driver: WebDriver) -> None: assert logout_button.is_displayed() assert logout_button.is_enabled() logout_button.click() + # SSO shows a "Do you want to log out?" confirmation page + confirm_button = wait_displayed( + driver, "//input[@value='Logout'] | //button[normalize-space(.)='Logout']" + ) + confirm_button.click() def admin_portal_logout(driver: WebDriver) -> None: @@ -331,19 +322,17 @@ def admin_portal_logout(driver: WebDriver) -> None: def vscode_login( driver: WebDriver, *, - device_login: bool = False, - **kwargs: Any, + device_login: bool = True, ) -> None: """Go through the login process to ansible and vscode. Args: driver: WebDriver instance - device_login: Whether to use device login flow - **kwargs: Additional arguments passed to sso_auth_flow + device_login: Whether to use device login flow (kept for compatibility) """ - vscode_connect(driver, device_login=device_login) + vscode_connect(driver) - sso_auth_flow(driver, **kwargs) + sso_auth_flow(driver) # switch back to vs-code driver.switch_to.window(driver.window_handles[0]) # user is now logged in, get a prediction @@ -462,70 +451,37 @@ def vscode_install_vsix(driver: WebDriver) -> None: def vscode_connect( driver: WebDriver, *, - user_menu: bool = False, - device_login: bool = False, + device_login: bool = True, install_vsix: bool = True, ) -> None: - """Go to the vscode ansible page and click the "connect" button. + """Go to the vscode ansible page and initiate the login flow. + + Uses the OAuth2 Device Flow via the F1 command palette. Args: driver: WebDriver instance - user_menu: Whether to use the VSCode Auth provider menu - device_login: Whether to use OAuth2 Device Flow + device_login: Whether to use device login flow (kept for compatibility) install_vsix: Whether to install the VSIX extension """ driver.get("http://127.0.0.1:8080") - # we rely on container backed in auto installation logic for our extension - # if install_vsix: - # vscode_install_vsix(driver) - - ansible_button = wait_displayed(driver, "//a[@aria-label='Ansible']", timeout=60) - - if device_login: # OAuth2 Device Flow - # in this case, give time for the command input to load correctly - vscode_run_command(driver, ">Ansible Lightspeed: Sign in with Red Hat") - if user_menu: # Use the VSCode Auth provider menu - user_button = click_and_wait( - driver, - ansible_button, - "//div[@aria-label='Accounts - Sign in requested']", - timeout=10, - ) - if user_button: - connect_button = click_and_wait( - driver, - user_button, - "//a[normalize-space(.)='Sign in with Ansible Lightspeed to use Ansible (1)']", - ) - if connect_button: - open_button = click_and_wait( - driver, - connect_button, - "//a[normalize-space(.)='Open']", - timeout=10, - ) - if open_button: - open_button.click() - else: - ansible_button.click() - connect_button = get_connect_button(driver) - connect_button.click() - driver.switch_to.default_content() - allow_button = wait_displayed( - driver, - "//a[normalize-space(.)='Allow']", - timeout=10, - ) - open_button = click_and_wait( - driver, - allow_button, - "//a[normalize-space(.)='Open']", - timeout=10, - ) + wait_displayed(driver, "//a[@aria-label='Ansible']", timeout=60) - if open_button: - open_button.click() + vscode_run_command_f1(driver, "Ansible Lightspeed: Sign in with Red Hat") + driver.switch_to.default_content() + allow_button = wait_displayed( + driver, + "//a[normalize-space(.)='Allow']", + timeout=10, + ) + open_button = click_and_wait( + driver, + allow_button, + "//a[normalize-space(.)='Open']", + timeout=10, + ) + if open_button: + open_button.click() driver.switch_to.window(driver.window_handles[-1]) @@ -704,74 +660,6 @@ def vscode_prediction( return prediction_preview -def vscode_trial_button( - driver: WebDriver, - file_name: str, - playbook: str, -) -> WebElement | None: - """Return the Trial button. - - Args: - driver: WebDriver instance - file_name: Name of the file to open - playbook: Playbook content to input - - Returns: - The Trial button element or None - """ - try: # in case explorer is already open - wait_displayed(driver, f"//span[text()='{file_name}']", timeout=1).click() - except ( - TimeoutException, - TimeOutError, - ElementClickInterceptedException, - ): # pragma: no cover - # go to explorer - explorer = wait_displayed( - driver, - "//a[contains(@aria-label, 'Explorer')]", - timeout=60, - ) - explorer.click() - # open the playbook - try: - wait_displayed(driver, f"//span[text()='{file_name}']", timeout=60).click() - except ElementClickInterceptedException: # pragma: no cover - ActionChains(driver).move_to_element(explorer).move_by_offset( - 0, - 50, - ).perform() - wait_displayed(driver, f"//span[text()='{file_name}']", timeout=60).click() - # click the text area to be able to input - clear_text(driver) - wait_displayed( - driver, - "//div[@class='view-lines monaco-mouse-cursor-text']", - timeout=60, - ).click() - lines = playbook.split("\n") - # input the content with low-level interactions - actions = ActionChains(driver) - for line in lines: - actions.send_keys(line) - actions.send_keys(Keys.ENTER) - actions.perform() - max_attempts = 4 - for n in range(max_attempts): - vscode_run_command(driver, ">Ansible Lightspeed: Inline suggestion trigger") - time.sleep(0.5) - try: - return wait_displayed( - driver, - "//a[contains(text(), 'Start a trial')]", - timeout=10, - ) - except TimeoutException: # pragma: no cover - if n == max_attempts - 1: - raise - return None - - def clear_text(driver: WebDriver) -> None: """Clear all the text in the vscode text code editor. @@ -827,18 +715,7 @@ def vscode_explanation(driver: WebDriver) -> str: timeout=60, ) text_window.click() - # right-click - actions = ActionChains(driver) - actions.context_click(text_window).perform() - - # Move cursor to the "Explain the playbook with Ansible Lightspeed" menu item. - # Note: The required number of DOWN key presses varies by VSCode version. - # Update the following line if VSCode's context menu items change. - for _ in range(POSITION_OF_ANSIBLE_EXPLAIN): - actions.send_keys(Keys.DOWN) - - actions.send_keys(Keys.ENTER) - actions.perform() + vscode_run_command_f1(driver, "Explain the playbook with Ansible Lightspeed") time.sleep(20) # move to new iframe # NOTE: If the test fails at this point (Explain panel doesn't appear), @@ -875,7 +752,7 @@ def vscode_playbook_generation(driver: WebDriver, task: str) -> tuple[str, str]: Tuple of (steps, playbook) text """ # run gen command - vscode_run_command(driver, ">Ansible Lightspeed: Playbook generation") + vscode_run_command_f1(driver, "Ansible Lightspeed: Playbook generation") title = find_element_across_iframes( driver, "//h2[contains(text(), 'Create a playbook with Ansible Lightspeed')]", @@ -945,7 +822,7 @@ def vscode_role_generation(driver: WebDriver, task: str) -> tuple[str, str]: Tuple of (steps, tasks) text """ # run gen command - vscode_run_command(driver, ">Ansible Lightspeed: Role generation") + vscode_run_command_f1(driver, "Ansible Lightspeed: Role generation") title = find_element_across_iframes( driver, "//h2[contains(text(), 'Create a role with Ansible Lightspeed')]", @@ -1063,6 +940,41 @@ def vscode_run_command( actions.send_keys(command_param, Keys.ENTER).perform() +def vscode_run_command_f1( + driver: WebDriver, command: str, command_param: str | None = None +) -> None: + """Run a command on vscode using F1 shortcut. + + Uses the F1 keyboard shortcut to open the command palette instead of + clicking the command center UI element, which is more resilient to + VSCode UI changes. + + Args: + driver: WebDriver instance + command: Command to run (will be prefixed with '>' if not present) + command_param: Parameter to pass to the command (argument) + """ + driver.switch_to.default_content() + if not command.startswith(">"): + command = ">" + command + actions = ActionChains(driver) + actions.send_keys(Keys.F1).perform() + time.sleep(0.5) + actions.send_keys(command).perform() + command_label = command.lstrip(">") + try: + item = wait_displayed( + driver, + f"//div[@class='quick-input-list']//span[contains(normalize-space(.), '{command_label}')]", + timeout=3, + ) + item.click() + except (TimeoutException, TimeOutError): # pragma: no cover + actions.send_keys(Keys.ENTER).perform() + if command_param: + actions.send_keys(command_param, Keys.ENTER).perform() + + def get_vscode_attribution(driver: WebDriver, prompt: str) -> dict[str, dict[str, str]]: """Return a dict for the attribution that is currently displayed.