mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
fix: add missing type hints and improve test logging (#12810)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from json_repair import repair_json
|
from json_repair import repair_json
|
||||||
from litellm.types.utils import ModelResponse
|
from litellm.types.utils import ModelResponse
|
||||||
@@ -32,7 +33,7 @@ class OpenHandsJSONEncoder(json.JSONEncoder):
|
|||||||
_json_encoder = OpenHandsJSONEncoder()
|
_json_encoder = OpenHandsJSONEncoder()
|
||||||
|
|
||||||
|
|
||||||
def dumps(obj, **kwargs):
|
def dumps(obj, **kwargs) -> str:
|
||||||
"""Serialize an object to str format"""
|
"""Serialize an object to str format"""
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
return _json_encoder.encode(obj)
|
return _json_encoder.encode(obj)
|
||||||
@@ -47,7 +48,7 @@ def dumps(obj, **kwargs):
|
|||||||
return json.dumps(obj, **encoder_kwargs)
|
return json.dumps(obj, **encoder_kwargs)
|
||||||
|
|
||||||
|
|
||||||
def loads(json_str, **kwargs):
|
def loads(json_str: str, **kwargs) -> Any:
|
||||||
"""Create a JSON object from str"""
|
"""Create a JSON object from str"""
|
||||||
try:
|
try:
|
||||||
return json.loads(json_str, **kwargs)
|
return json.loads(json_str, **kwargs)
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ then ensures the GitHub token is set in Settings → Integrations and that the
|
|||||||
home screen shows the repository selector.
|
home screen shows the repository selector.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from playwright.sync_api import Page, expect
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def test_github_token_configuration(page: Page, base_url: str):
|
def test_github_token_configuration(page: Page, base_url: str):
|
||||||
"""
|
"""
|
||||||
@@ -28,51 +31,51 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
base_url = 'http://localhost:12000'
|
base_url = 'http://localhost:12000'
|
||||||
|
|
||||||
# Navigate to the OpenHands application
|
# Navigate to the OpenHands application
|
||||||
print(f'Step 1: Navigating to OpenHands application at {base_url}...')
|
logger.info(f'Step 1: Navigating to OpenHands application at {base_url}...')
|
||||||
page.goto(base_url)
|
page.goto(base_url)
|
||||||
page.wait_for_load_state('networkidle', timeout=30000)
|
page.wait_for_load_state('networkidle', timeout=30000)
|
||||||
|
|
||||||
# Take initial screenshot
|
# Take initial screenshot
|
||||||
page.screenshot(path='test-results/token_01_initial_load.png')
|
page.screenshot(path='test-results/token_01_initial_load.png')
|
||||||
print('Screenshot saved: token_01_initial_load.png')
|
logger.info('Screenshot saved: token_01_initial_load.png')
|
||||||
|
|
||||||
# Step 1.5: Handle any initial modals that might appear (LLM API key configuration)
|
# Step 1.5: Handle any initial modals that might appear (LLM API key configuration)
|
||||||
try:
|
try:
|
||||||
# Check for AI Provider Configuration modal
|
# Check for AI Provider Configuration modal
|
||||||
config_modal = page.locator('text=AI Provider Configuration')
|
config_modal = page.locator('text=AI Provider Configuration')
|
||||||
if config_modal.is_visible(timeout=5000):
|
if config_modal.is_visible(timeout=5000):
|
||||||
print('AI Provider Configuration modal detected')
|
logger.info('AI Provider Configuration modal detected')
|
||||||
|
|
||||||
# Fill in the LLM API key if available
|
# Fill in the LLM API key if available
|
||||||
llm_api_key_input = page.locator('[data-testid="llm-api-key-input"]')
|
llm_api_key_input = page.locator('[data-testid="llm-api-key-input"]')
|
||||||
if llm_api_key_input.is_visible(timeout=3000):
|
if llm_api_key_input.is_visible(timeout=3000):
|
||||||
llm_api_key = os.getenv('LLM_API_KEY', 'test-key')
|
llm_api_key = os.getenv('LLM_API_KEY', 'test-key')
|
||||||
llm_api_key_input.fill(llm_api_key)
|
llm_api_key_input.fill(llm_api_key)
|
||||||
print(f'Filled LLM API key (length: {len(llm_api_key)})')
|
logger.info(f'Filled LLM API key (length: {len(llm_api_key)})')
|
||||||
|
|
||||||
# Click the Save button
|
# Click the Save button
|
||||||
save_button = page.locator('button:has-text("Save")')
|
save_button = page.locator('button:has-text("Save")')
|
||||||
if save_button.is_visible(timeout=3000):
|
if save_button.is_visible(timeout=3000):
|
||||||
save_button.click()
|
save_button.click()
|
||||||
page.wait_for_timeout(2000)
|
page.wait_for_timeout(2000)
|
||||||
print('Saved LLM API key configuration')
|
logger.info('Saved LLM API key configuration')
|
||||||
|
|
||||||
# Check for Privacy Preferences modal
|
# Check for Privacy Preferences modal
|
||||||
privacy_modal = page.locator('text=Your Privacy Preferences')
|
privacy_modal = page.locator('text=Your Privacy Preferences')
|
||||||
if privacy_modal.is_visible(timeout=5000):
|
if privacy_modal.is_visible(timeout=5000):
|
||||||
print('Privacy Preferences modal detected')
|
logger.info('Privacy Preferences modal detected')
|
||||||
confirm_button = page.locator('button:has-text("Confirm Preferences")')
|
confirm_button = page.locator('button:has-text("Confirm Preferences")')
|
||||||
if confirm_button.is_visible(timeout=3000):
|
if confirm_button.is_visible(timeout=3000):
|
||||||
confirm_button.click()
|
confirm_button.click()
|
||||||
page.wait_for_timeout(2000)
|
page.wait_for_timeout(2000)
|
||||||
print('Confirmed privacy preferences')
|
logger.info('Confirmed privacy preferences')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Error handling initial modals: {e}')
|
logger.error(f'Error handling initial modals: {e}')
|
||||||
page.screenshot(path='test-results/token_01_5_modal_error.png')
|
page.screenshot(path='test-results/token_01_5_modal_error.png')
|
||||||
print('Screenshot saved: token_01_5_modal_error.png')
|
logger.info('Screenshot saved: token_01_5_modal_error.png')
|
||||||
|
|
||||||
# Step 2: Check if GitHub token is already configured or needs to be set
|
# Step 2: Check if GitHub token is already configured or needs to be set
|
||||||
print('Step 2: Checking if GitHub token is configured...')
|
logger.info('Step 2: Checking if GitHub token is configured...')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# First, check if we're already on the home screen with repository selection
|
# First, check if we're already on the home screen with repository selection
|
||||||
@@ -80,7 +83,7 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
connect_to_provider = page.locator('text=Connect to a Repository')
|
connect_to_provider = page.locator('text=Connect to a Repository')
|
||||||
|
|
||||||
if connect_to_provider.is_visible(timeout=3000):
|
if connect_to_provider.is_visible(timeout=3000):
|
||||||
print('Found "Connect to a Repository" section')
|
logger.info('Found "Connect to a Repository" section')
|
||||||
|
|
||||||
# Check if we need to configure a provider (GitHub token)
|
# Check if we need to configure a provider (GitHub token)
|
||||||
navigate_to_settings_button = page.locator(
|
navigate_to_settings_button = page.locator(
|
||||||
@@ -88,7 +91,9 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if navigate_to_settings_button.is_visible(timeout=3000):
|
if navigate_to_settings_button.is_visible(timeout=3000):
|
||||||
print('GitHub token not configured. Need to navigate to settings...')
|
logger.info(
|
||||||
|
'GitHub token not configured. Need to navigate to settings...'
|
||||||
|
)
|
||||||
|
|
||||||
# Click the Settings button to navigate to the settings page
|
# Click the Settings button to navigate to the settings page
|
||||||
navigate_to_settings_button.click()
|
navigate_to_settings_button.click()
|
||||||
@@ -96,19 +101,21 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
page.wait_for_timeout(3000) # Wait for navigation to complete
|
page.wait_for_timeout(3000) # Wait for navigation to complete
|
||||||
|
|
||||||
# We should now be on the /settings/integrations page
|
# We should now be on the /settings/integrations page
|
||||||
print('Navigated to settings page, looking for GitHub token input...')
|
logger.info(
|
||||||
|
'Navigated to settings page, looking for GitHub token input...'
|
||||||
|
)
|
||||||
|
|
||||||
# Check if we're on the settings page with the integrations tab
|
# Check if we're on the settings page with the integrations tab
|
||||||
settings_screen = page.locator('[data-testid="settings-screen"]')
|
settings_screen = page.locator('[data-testid="settings-screen"]')
|
||||||
if settings_screen.is_visible(timeout=5000):
|
if settings_screen.is_visible(timeout=5000):
|
||||||
print('Settings screen is visible')
|
logger.info('Settings screen is visible')
|
||||||
|
|
||||||
# Make sure we're on the Integrations tab
|
# Make sure we're on the Integrations tab
|
||||||
integrations_tab = page.locator('text=Integrations')
|
integrations_tab = page.locator('text=Integrations')
|
||||||
if integrations_tab.is_visible(timeout=3000):
|
if integrations_tab.is_visible(timeout=3000):
|
||||||
# Check if we need to click the tab
|
# Check if we need to click the tab
|
||||||
if not page.url.endswith('/settings/integrations'):
|
if not page.url.endswith('/settings/integrations'):
|
||||||
print('Clicking Integrations tab...')
|
logger.info('Clicking Integrations tab...')
|
||||||
integrations_tab.click()
|
integrations_tab.click()
|
||||||
page.wait_for_load_state('networkidle')
|
page.wait_for_load_state('networkidle')
|
||||||
page.wait_for_timeout(2000)
|
page.wait_for_timeout(2000)
|
||||||
@@ -118,7 +125,7 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
'[data-testid="github-token-input"]'
|
'[data-testid="github-token-input"]'
|
||||||
)
|
)
|
||||||
if github_token_input.is_visible(timeout=5000):
|
if github_token_input.is_visible(timeout=5000):
|
||||||
print('Found GitHub token input field')
|
logger.info('Found GitHub token input field')
|
||||||
|
|
||||||
# Fill in the GitHub token from environment variable
|
# Fill in the GitHub token from environment variable
|
||||||
github_token = os.getenv('GITHUB_TOKEN', '')
|
github_token = os.getenv('GITHUB_TOKEN', '')
|
||||||
@@ -126,18 +133,18 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
# Clear the field first, then fill it
|
# Clear the field first, then fill it
|
||||||
github_token_input.clear()
|
github_token_input.clear()
|
||||||
github_token_input.fill(github_token)
|
github_token_input.fill(github_token)
|
||||||
print(
|
logger.info(
|
||||||
f'Filled GitHub token from environment variable (length: {len(github_token)})'
|
f'Filled GitHub token from environment variable (length: {len(github_token)})'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify the token was filled
|
# Verify the token was filled
|
||||||
filled_value = github_token_input.input_value()
|
filled_value = github_token_input.input_value()
|
||||||
if filled_value:
|
if filled_value:
|
||||||
print(
|
logger.info(
|
||||||
f'Token field now contains value of length: {len(filled_value)}'
|
f'Token field now contains value of length: {len(filled_value)}'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
logger.warning(
|
||||||
'WARNING: Token field appears to be empty after filling'
|
'WARNING: Token field appears to be empty after filling'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,12 +153,12 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
if save_button.is_visible(timeout=3000):
|
if save_button.is_visible(timeout=3000):
|
||||||
# Check if button is enabled
|
# Check if button is enabled
|
||||||
is_disabled = save_button.is_disabled()
|
is_disabled = save_button.is_disabled()
|
||||||
print(
|
logger.info(
|
||||||
f'Save Changes button found, disabled: {is_disabled}'
|
f'Save Changes button found, disabled: {is_disabled}'
|
||||||
)
|
)
|
||||||
|
|
||||||
if not is_disabled:
|
if not is_disabled:
|
||||||
print('Clicking Save Changes button...')
|
logger.info('Clicking Save Changes button...')
|
||||||
save_button.click()
|
save_button.click()
|
||||||
|
|
||||||
# Wait for the save operation to complete
|
# Wait for the save operation to complete
|
||||||
@@ -164,46 +171,52 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
'document.querySelector(\'[data-testid="submit-button"]\').disabled === true',
|
'document.querySelector(\'[data-testid="submit-button"]\').disabled === true',
|
||||||
timeout=10000,
|
timeout=10000,
|
||||||
)
|
)
|
||||||
print(
|
logger.info(
|
||||||
'Save operation completed - form is now clean'
|
'Save operation completed - form is now clean'
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
print(
|
logger.warning(
|
||||||
'Save operation completed (timeout waiting for form clean state)'
|
'Save operation completed (timeout waiting for form clean state)'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Navigate back to home page after successful save
|
# Navigate back to home page after successful save
|
||||||
print('Navigating back to home page...')
|
logger.info('Navigating back to home page...')
|
||||||
page.goto(base_url)
|
page.goto(base_url)
|
||||||
page.wait_for_load_state('networkidle')
|
page.wait_for_load_state('networkidle')
|
||||||
page.wait_for_timeout(
|
page.wait_for_timeout(
|
||||||
5000
|
5000
|
||||||
) # Wait longer for providers to be updated
|
) # Wait longer for providers to be updated
|
||||||
else:
|
else:
|
||||||
print(
|
logger.warning(
|
||||||
'Save Changes button is disabled - form may be invalid'
|
'Save Changes button is disabled - form may be invalid'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print('Save Changes button not found')
|
logger.warning('Save Changes button not found')
|
||||||
else:
|
else:
|
||||||
print('No GitHub token found in environment variables')
|
logger.warning(
|
||||||
|
'No GitHub token found in environment variables'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print('GitHub token input field not found on settings page')
|
logger.warning(
|
||||||
|
'GitHub token input field not found on settings page'
|
||||||
|
)
|
||||||
# Take a screenshot to see what's on the page
|
# Take a screenshot to see what's on the page
|
||||||
page.screenshot(path='test-results/token_02_settings_debug.png')
|
page.screenshot(path='test-results/token_02_settings_debug.png')
|
||||||
print('Debug screenshot saved: token_02_settings_debug.png')
|
logger.info(
|
||||||
|
'Debug screenshot saved: token_02_settings_debug.png'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print('Settings screen not found')
|
logger.warning('Settings screen not found')
|
||||||
else:
|
else:
|
||||||
# Branch 2: GitHub token is already configured, repository selection is available
|
# Branch 2: GitHub token is already configured, repository selection is available
|
||||||
print(
|
logger.info(
|
||||||
'GitHub token is already configured, repository selection is available'
|
'GitHub token is already configured, repository selection is available'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if we need to update the token by going to settings manually
|
# Check if we need to update the token by going to settings manually
|
||||||
settings_button = page.locator('button:has-text("Settings")')
|
settings_button = page.locator('button:has-text("Settings")')
|
||||||
if settings_button.is_visible(timeout=3000):
|
if settings_button.is_visible(timeout=3000):
|
||||||
print(
|
logger.info(
|
||||||
'Settings button found, clicking to navigate to settings page...'
|
'Settings button found, clicking to navigate to settings page...'
|
||||||
)
|
)
|
||||||
settings_button.click()
|
settings_button.click()
|
||||||
@@ -213,7 +226,7 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
# Navigate to the Integrations tab
|
# Navigate to the Integrations tab
|
||||||
integrations_tab = page.locator('text=Integrations')
|
integrations_tab = page.locator('text=Integrations')
|
||||||
if integrations_tab.is_visible(timeout=3000):
|
if integrations_tab.is_visible(timeout=3000):
|
||||||
print('Clicking Integrations tab...')
|
logger.info('Clicking Integrations tab...')
|
||||||
integrations_tab.click()
|
integrations_tab.click()
|
||||||
page.wait_for_load_state('networkidle')
|
page.wait_for_load_state('networkidle')
|
||||||
page.wait_for_timeout(2000)
|
page.wait_for_timeout(2000)
|
||||||
@@ -223,7 +236,7 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
'[data-testid="github-token-input"]'
|
'[data-testid="github-token-input"]'
|
||||||
)
|
)
|
||||||
if github_token_input.is_visible(timeout=5000):
|
if github_token_input.is_visible(timeout=5000):
|
||||||
print('Found GitHub token input field')
|
logger.info('Found GitHub token input field')
|
||||||
|
|
||||||
# Fill in the GitHub token from environment variable
|
# Fill in the GitHub token from environment variable
|
||||||
github_token = os.getenv('GITHUB_TOKEN', '')
|
github_token = os.getenv('GITHUB_TOKEN', '')
|
||||||
@@ -231,7 +244,7 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
# Clear the field first, then fill it
|
# Clear the field first, then fill it
|
||||||
github_token_input.clear()
|
github_token_input.clear()
|
||||||
github_token_input.fill(github_token)
|
github_token_input.fill(github_token)
|
||||||
print(
|
logger.info(
|
||||||
f'Filled GitHub token from environment variable (length: {len(github_token)})'
|
f'Filled GitHub token from environment variable (length: {len(github_token)})'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -243,52 +256,56 @@ def test_github_token_configuration(page: Page, base_url: str):
|
|||||||
save_button.is_visible(timeout=3000)
|
save_button.is_visible(timeout=3000)
|
||||||
and not save_button.is_disabled()
|
and not save_button.is_disabled()
|
||||||
):
|
):
|
||||||
print('Clicking Save Changes button...')
|
logger.info('Clicking Save Changes button...')
|
||||||
save_button.click()
|
save_button.click()
|
||||||
page.wait_for_timeout(3000)
|
page.wait_for_timeout(3000)
|
||||||
|
|
||||||
# Navigate back to home page
|
# Navigate back to home page
|
||||||
print('Navigating back to home page...')
|
logger.info('Navigating back to home page...')
|
||||||
page.goto(base_url)
|
page.goto(base_url)
|
||||||
page.wait_for_load_state('networkidle')
|
page.wait_for_load_state('networkidle')
|
||||||
page.wait_for_timeout(3000)
|
page.wait_for_timeout(3000)
|
||||||
else:
|
else:
|
||||||
print(
|
logger.warning(
|
||||||
'GitHub token input field not found, going back to home page'
|
'GitHub token input field not found, going back to home page'
|
||||||
)
|
)
|
||||||
page.goto(base_url)
|
page.goto(base_url)
|
||||||
page.wait_for_load_state('networkidle')
|
page.wait_for_load_state('networkidle')
|
||||||
else:
|
else:
|
||||||
print('Integrations tab not found, going back to home page')
|
logger.warning(
|
||||||
|
'Integrations tab not found, going back to home page'
|
||||||
|
)
|
||||||
page.goto(base_url)
|
page.goto(base_url)
|
||||||
page.wait_for_load_state('networkidle')
|
page.wait_for_load_state('networkidle')
|
||||||
else:
|
else:
|
||||||
print('Settings button not found, continuing with existing token')
|
logger.info(
|
||||||
|
'Settings button not found, continuing with existing token'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print('Could not find "Connect to a Repository" section')
|
logger.warning('Could not find "Connect to a Repository" section')
|
||||||
|
|
||||||
page.screenshot(path='test-results/token_03_after_settings.png')
|
page.screenshot(path='test-results/token_03_after_settings.png')
|
||||||
print('Screenshot saved: token_03_after_settings.png')
|
logger.info('Screenshot saved: token_03_after_settings.png')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Error checking GitHub token configuration: {e}')
|
logger.error(f'Error checking GitHub token configuration: {e}')
|
||||||
page.screenshot(path='test-results/token_04_error.png')
|
page.screenshot(path='test-results/token_04_error.png')
|
||||||
print('Screenshot saved: token_04_error.png')
|
logger.info('Screenshot saved: token_04_error.png')
|
||||||
|
|
||||||
# Step 3: Verify we're back on the home screen with repository selection available
|
# Step 3: Verify we're back on the home screen with repository selection available
|
||||||
print('Step 3: Verifying repository selection is available...')
|
logger.info('Step 3: Verifying repository selection is available...')
|
||||||
|
|
||||||
# Wait for the home screen to load
|
# Wait for the home screen to load
|
||||||
home_screen = page.locator('[data-testid="home-screen"]')
|
home_screen = page.locator('[data-testid="home-screen"]')
|
||||||
expect(home_screen).to_be_visible(timeout=15000)
|
expect(home_screen).to_be_visible(timeout=15000)
|
||||||
print('Home screen is visible')
|
logger.info('Home screen is visible')
|
||||||
|
|
||||||
# Look for the repository dropdown/selector
|
# Look for the repository dropdown/selector
|
||||||
repo_dropdown = page.locator('[data-testid="repo-dropdown"]')
|
repo_dropdown = page.locator('[data-testid="repo-dropdown"]')
|
||||||
expect(repo_dropdown).to_be_visible(timeout=15000)
|
expect(repo_dropdown).to_be_visible(timeout=15000)
|
||||||
print('Repository dropdown is visible')
|
logger.info('Repository dropdown is visible')
|
||||||
|
|
||||||
# Success - we've verified the GitHub token configuration
|
# Success - we've verified the GitHub token configuration
|
||||||
print('GitHub token configuration verified successfully')
|
logger.info('GitHub token configuration verified successfully')
|
||||||
page.screenshot(path='test-results/token_05_success.png')
|
page.screenshot(path='test-results/token_05_success.png')
|
||||||
print('Screenshot saved: token_05_success.png')
|
logger.info('Screenshot saved: token_05_success.png')
|
||||||
|
|||||||
Reference in New Issue
Block a user