test_accessibility.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. """
  2. Tests for the accessibility plugin.
  3. Tests the real accessibility hook with an actual URL to verify
  4. accessibility tree and page outline extraction.
  5. """
  6. import json
  7. import shutil
  8. import subprocess
  9. import sys
  10. import tempfile
  11. from pathlib import Path
  12. import pytest
  13. from django.test import TestCase
  14. # Import chrome test helpers
  15. sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'chrome' / 'tests'))
  16. from chrome_test_helpers import (
  17. chrome_session,
  18. get_test_env,
  19. get_plugin_dir,
  20. get_hook_script,
  21. )
  22. def chrome_available() -> bool:
  23. """Check if Chrome/Chromium is available."""
  24. for name in ['chromium', 'chromium-browser', 'google-chrome', 'chrome']:
  25. if shutil.which(name):
  26. return True
  27. return False
  28. # Get the path to the accessibility hook
  29. PLUGIN_DIR = get_plugin_dir(__file__)
  30. ACCESSIBILITY_HOOK = get_hook_script(PLUGIN_DIR, 'on_Snapshot__*_accessibility.*')
  31. class TestAccessibilityPlugin(TestCase):
  32. """Test the accessibility plugin."""
  33. def test_accessibility_hook_exists(self):
  34. """Accessibility hook script should exist."""
  35. self.assertIsNotNone(ACCESSIBILITY_HOOK, "Accessibility hook not found in plugin directory")
  36. self.assertTrue(ACCESSIBILITY_HOOK.exists(), f"Hook not found: {ACCESSIBILITY_HOOK}")
  37. class TestAccessibilityWithChrome(TestCase):
  38. """Integration tests for accessibility plugin with Chrome."""
  39. def setUp(self):
  40. """Set up test environment."""
  41. self.temp_dir = Path(tempfile.mkdtemp())
  42. def tearDown(self):
  43. """Clean up."""
  44. shutil.rmtree(self.temp_dir, ignore_errors=True)
  45. def test_accessibility_extracts_page_outline(self):
  46. """Accessibility hook should extract headings and accessibility tree."""
  47. test_url = 'https://example.com'
  48. snapshot_id = 'test-accessibility-snapshot'
  49. try:
  50. with chrome_session(
  51. self.temp_dir,
  52. crawl_id='test-accessibility-crawl',
  53. snapshot_id=snapshot_id,
  54. test_url=test_url,
  55. navigate=True,
  56. timeout=30,
  57. ) as (chrome_process, chrome_pid, snapshot_chrome_dir, env):
  58. # Use the environment from chrome_session (already has CHROME_HEADLESS=true)
  59. # Run accessibility hook with the active Chrome session
  60. result = subprocess.run(
  61. ['node', str(ACCESSIBILITY_HOOK), f'--url={test_url}', f'--snapshot-id={snapshot_id}'],
  62. cwd=str(snapshot_chrome_dir),
  63. capture_output=True,
  64. text=True,
  65. timeout=60,
  66. env=env
  67. )
  68. # Check for output file
  69. accessibility_output = snapshot_chrome_dir / 'accessibility.json'
  70. accessibility_data = None
  71. # Try parsing from file first
  72. if accessibility_output.exists():
  73. with open(accessibility_output) as f:
  74. try:
  75. accessibility_data = json.load(f)
  76. except json.JSONDecodeError:
  77. pass
  78. # Verify hook ran successfully
  79. self.assertEqual(result.returncode, 0, f"Hook failed: {result.stderr}")
  80. self.assertNotIn('Traceback', result.stderr)
  81. # example.com has headings, so we should get accessibility data
  82. self.assertIsNotNone(accessibility_data, "No accessibility data was generated")
  83. # Verify we got page outline data
  84. self.assertIn('headings', accessibility_data, f"Missing headings: {accessibility_data}")
  85. self.assertIn('url', accessibility_data, f"Missing url: {accessibility_data}")
  86. except RuntimeError:
  87. raise
  88. def test_accessibility_disabled_skips(self):
  89. """Test that ACCESSIBILITY_ENABLED=False skips without error."""
  90. test_url = 'https://example.com'
  91. snapshot_id = 'test-disabled'
  92. env = get_test_env()
  93. env['ACCESSIBILITY_ENABLED'] = 'False'
  94. result = subprocess.run(
  95. ['node', str(ACCESSIBILITY_HOOK), f'--url={test_url}', f'--snapshot-id={snapshot_id}'],
  96. cwd=str(self.temp_dir),
  97. capture_output=True,
  98. text=True,
  99. timeout=30,
  100. env=env
  101. )
  102. # Should exit 0 even when disabled
  103. self.assertEqual(result.returncode, 0, f"Should succeed when disabled: {result.stderr}")
  104. # Should NOT create output file when disabled
  105. accessibility_output = self.temp_dir / 'accessibility.json'
  106. self.assertFalse(accessibility_output.exists(), "Should not create file when disabled")
  107. def test_accessibility_missing_url_argument(self):
  108. """Test that missing --url argument causes error."""
  109. snapshot_id = 'test-missing-url'
  110. result = subprocess.run(
  111. ['node', str(ACCESSIBILITY_HOOK), f'--snapshot-id={snapshot_id}'],
  112. cwd=str(self.temp_dir),
  113. capture_output=True,
  114. text=True,
  115. timeout=30,
  116. env=get_test_env()
  117. )
  118. # Should fail with non-zero exit code
  119. self.assertNotEqual(result.returncode, 0, "Should fail when URL missing")
  120. def test_accessibility_missing_snapshot_id_argument(self):
  121. """Test that missing --snapshot-id argument causes error."""
  122. test_url = 'https://example.com'
  123. result = subprocess.run(
  124. ['node', str(ACCESSIBILITY_HOOK), f'--url={test_url}'],
  125. cwd=str(self.temp_dir),
  126. capture_output=True,
  127. text=True,
  128. timeout=30,
  129. env=get_test_env()
  130. )
  131. # Should fail with non-zero exit code
  132. self.assertNotEqual(result.returncode, 0, "Should fail when snapshot-id missing")
  133. def test_accessibility_with_no_chrome_session(self):
  134. """Test that hook fails gracefully when no Chrome session exists."""
  135. test_url = 'https://example.com'
  136. snapshot_id = 'test-no-chrome'
  137. result = subprocess.run(
  138. ['node', str(ACCESSIBILITY_HOOK), f'--url={test_url}', f'--snapshot-id={snapshot_id}'],
  139. cwd=str(self.temp_dir),
  140. capture_output=True,
  141. text=True,
  142. timeout=30,
  143. env=get_test_env()
  144. )
  145. # Should fail when no Chrome session
  146. self.assertNotEqual(result.returncode, 0, "Should fail when no Chrome session exists")
  147. # Error should mention CDP or Chrome
  148. err_lower = result.stderr.lower()
  149. self.assertTrue(
  150. any(x in err_lower for x in ['chrome', 'cdp', 'cannot find', 'puppeteer']),
  151. f"Should mention Chrome/CDP in error: {result.stderr}"
  152. )
  153. if __name__ == '__main__':
  154. pytest.main([__file__, '-v'])