File size: 11,845 Bytes
9ebca63 ecb31fb 9ebca63 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | """Focused browser keyboard/accessible-name regressions for the floating Reader UI.
Controlled Chromium verifies DOM/AX contracts, not actual screen-reader speech or
universal WCAG conformance. Uses the existing pinned two-site Reader fixtures.
"""
import unittest
from urllib.parse import urlencode
from tests import test_reader_pdf_followup as support
class ReaderAccessibilityTests(unittest.TestCase):
setUpClass = classmethod(support.PdfFollowupTests.setUpClass.__func__)
setUp = support.PdfFollowupTests.setUp
def open_book(self, extension='md', body=None):
self.page.emulate_media(reduced_motion='reduce')
self.page.route('**/api/reader-content**', lambda route: route.fulfill(
content_type='text/plain', body=body or '# Chapter one\n\n' + 'needle body\n\n' * 600 + '# Chapter two\n\nEnd'))
self.page.goto(self.origin + support.PREFIX + '/static/reader.html?' + urlencode(dict(
url='https://huggingface.co/datasets/VoiceOfML/Test/resolve/main/accessibility.' + extension,
ext=extension)))
self.page.wait_for_function("()=>['ready','failed'].includes(document.documentElement.dataset.readerPhase)")
if extension == 'md':
self.page.wait_for_function("()=>document.querySelector('#history').getAttribute('aria-expanded')==='true'")
def open_with_keyboard(self):
if self.page.locator('#history').get_attribute('aria-expanded') == 'true':
self.page.locator('#history').press('Enter')
self.page.locator('#history').press('Enter')
def test_forced_colors_keeps_bookmark_and_selected_controls_visible(self):
self.open_book()
self.page.emulate_media(forced_colors='active', reduced_motion='reduce')
ribbon = self.page.locator('#bookmark-ribbon')
self.page.wait_for_function('()=>getComputedStyle(document.querySelector("#bookmark-ribbon")).clipPath==="none"')
self.assertGreater(float(ribbon.evaluate('node=>parseFloat(getComputedStyle(node).borderTopWidth)')), 0)
self.assertIn('书签', ribbon.evaluate('node=>getComputedStyle(node,"::after").content'))
for theme in ('dark', 'light'):
self.page.evaluate('theme=>{document.documentElement.dataset.theme=theme}', theme)
self.open_with_keyboard()
self.assertNotEqual(self.page.locator('#toc-tab').evaluate('node=>getComputedStyle(node).outlineStyle'), 'none')
self.page.keyboard.press('Escape')
ribbon.focus()
self.assertNotEqual(ribbon.evaluate('node=>getComputedStyle(node).outlineStyle'), 'none')
self.assertEqual(self.page.locator('.reader-markdown').evaluate('node=>getComputedStyle(node).filter'), 'none')
def test_names_relations_auto_open_and_keyboard_focus(self):
self.page.set_viewport_size({'width': 1200, 'height': 800})
self.open_book()
self.assertFalse(self.page.evaluate("document.querySelector('#history-panel').contains(document.activeElement)"))
self.page.keyboard.press('Tab')
self.assertTrue(self.page.locator('.reader-skip-link').evaluate('node=>node===document.activeElement'))
self.page.keyboard.press('Tab')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'back')
self.assertEqual(self.page.get_by_role('complementary', name='阅读选项').count(), 1)
self.assertEqual(self.page.get_by_role('button', name='关闭阅读选项', exact=True).count(), 1)
self.assertEqual(self.page.locator('#history').get_attribute('aria-controls'), 'history-panel')
self.open_with_keyboard()
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'toc-tab')
self.page.keyboard.press('ArrowRight')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'bookmarks-tab')
self.page.keyboard.press('Home')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'toc-tab')
self.page.keyboard.press('Escape')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'history')
self.assertTrue(self.page.locator('#history-panel').evaluate('node=>node.inert'))
self.page.locator('#full-search-toggle').evaluate('node=>node.focus()')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'history')
def test_skip_link_reaches_reading_surface_without_changing_position(self):
self.open_book()
self.page.locator('#history-close').click()
self.page.locator('#viewport').evaluate('node=>{node.scrollTop=450}')
before = self.page.locator('#viewport').evaluate('node=>node.scrollTop')
link = self.page.get_by_role('link', name='跳转到正文')
link.focus()
self.assertGreaterEqual(link.bounding_box()['y'], 0)
self.assertNotEqual(link.evaluate('node=>getComputedStyle(node).outlineStyle'), 'none')
link.press('Enter')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'viewport')
self.assertEqual(self.page.locator('#viewport').evaluate('node=>node.scrollTop'), before)
self.page.keyboard.press('PageDown')
self.page.wait_for_function('top=>document.querySelector("#viewport").scrollTop>top', arg=before)
before = self.page.locator('#viewport').evaluate('node=>node.scrollTop')
self.page.evaluate('''()=>{const host=document.createElement('span');
document.querySelector('#content').append(host);
const root=host.attachShadow({mode:'open'}),button=document.createElement('button');
button.textContent='Chapter action';button.onclick=()=>window.__chapterAction=true;
root.append(button);button.focus({preventScroll:true});}''')
self.page.keyboard.press('Space')
self.assertTrue(self.page.evaluate('!!window.__chapterAction'))
self.assertEqual(self.page.locator('#viewport').evaluate('node=>node.scrollTop'), before)
def test_search_escape_restores_focus_and_expansion_state(self):
self.open_book()
self.open_with_keyboard()
self.page.locator('#full-search-toggle').press('Enter')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'full-search-input')
self.assertEqual(self.page.locator('#full-search-toggle').get_attribute('aria-controls'), 'full-search-view')
self.assertEqual(self.page.locator('#full-search-toggle').get_attribute('aria-expanded'), 'true')
self.page.locator('#full-search-input').fill('needle')
self.page.wait_for_function("()=>document.querySelector('#full-search-status').textContent==='600 个结果'")
self.page.keyboard.press('Escape')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'full-search-toggle')
self.assertEqual(self.page.locator('#full-search-toggle').get_attribute('aria-expanded'), 'false')
self.assertEqual(self.page.locator('#history').get_attribute('aria-expanded'), 'true')
self.page.locator('#full-search-toggle').press('Enter')
self.page.locator('#bookmarks-tab').click()
self.assertEqual(self.page.locator('#full-search-toggle').get_attribute('aria-expanded'), 'false')
self.page.keyboard.press('Escape')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'history')
def test_directory_current_location_and_keys_do_not_scroll_the_book(self):
self.open_book()
self.assertEqual(self.page.locator('#toc-list [aria-current="location"]').count(), 1)
self.assertEqual(self.page.locator('.toc-current-mark[aria-hidden="true"]').count(), 2)
link = self.page.locator('#toc-list [role="link"]').first
link.focus()
before = self.page.locator('#viewport').evaluate('node=>node.scrollTop')
self.page.keyboard.press('ArrowDown')
self.assertEqual(self.page.locator('#viewport').evaluate('node=>node.scrollTop'), before)
self.page.locator('#toc-list [role="link"]').last.press('Enter')
self.page.wait_for_function('()=>document.querySelector("#history").getAttribute("aria-expanded")==="false"')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'history')
self.page.locator('#history').press('Enter')
self.page.wait_for_function('()=>document.querySelector("#toc-list .toc-item:last-child [aria-current=location]")')
self.page.get_by_role('button', name='搜索目录', exact=True).press('Enter')
field = self.page.locator('#toc-panel .panel-search')
self.assertTrue(field.evaluate('node=>node===document.activeElement'))
self.assertEqual(self.page.locator('#toc-panel .panel-search-toggle').get_attribute('aria-controls'), field.get_attribute('id'))
def test_keyboard_progress_can_undo_without_pointer_events(self):
self.open_book('txt', 'Long plain text\n' * 2000)
self.page.locator('#history').press('Enter')
slider = self.page.get_by_role('slider', name='阅读进度')
before = self.page.locator('#viewport').evaluate('node=>node.scrollTop')
slider.press('End')
self.page.wait_for_function('top=>document.querySelector("#viewport").scrollTop>top+100', arg=before)
self.assertEqual(slider.get_attribute('aria-valuetext'), '100.0%')
self.page.get_by_role('button', name='撤销进度调整').press('Enter')
self.page.wait_for_function('top=>Math.abs(document.querySelector("#viewport").scrollTop-top)<2', arg=before)
self.assertEqual(self.page.locator('.reader-progress-undo').count(), 1)
self.assertTrue(self.page.locator('.reader-progress-undo').evaluate('node=>node.hidden'))
self.assertTrue(slider.evaluate('node=>node===document.activeElement'))
def test_bookmark_names_delete_focus_and_modal_priority(self):
self.open_book('txt', 'A document\n' * 200)
self.page.evaluate('''async()=>{const url=new URL(location.href).searchParams.get('url');
for(const [id,label] of [['one','First bookmark'],['two','Second bookmark']])
await VoiceOfMLReaderStore.putBookmark({id,url,label,createdAt:id==='one'?2:1});}''')
self.page.locator('#history').press('Enter')
self.page.locator('#bookmarks-tab').press('Enter')
self.page.get_by_role('button', name='编辑书签:First bookmark', exact=True).press('Enter')
self.assertEqual(self.page.get_by_role('dialog').count(), 1)
self.page.keyboard.press('Shift+Tab')
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'bookmark-cancel')
self.page.keyboard.press('Escape')
self.assertEqual(self.page.locator('#history').get_attribute('aria-expanded'), 'true')
self.page.get_by_role('button', name='删除书签:First bookmark', exact=True).press('Enter')
self.page.wait_for_function("()=>document.querySelectorAll('#bookmarks-list .panel-item').length===1")
self.assertTrue(self.page.get_by_role('button', name='Second bookmark', exact=True).evaluate('node=>node===document.activeElement'))
self.page.get_by_role('button', name='删除书签:Second bookmark', exact=True).press('Enter')
self.page.wait_for_function("()=>!document.querySelector('#bookmarks-list .panel-item')")
self.assertEqual(self.page.evaluate('document.activeElement.id'), 'bookmarks-all')
def test_failure_is_announced_without_stealing_keyboard_focus(self):
self.open_book('unsupported', 'not supported')
self.assertEqual(self.page.get_by_role('alert').count(), 1)
self.assertIn('此文件暂不支持在线阅读', self.page.get_by_role('alert').inner_text())
self.assertFalse(self.page.evaluate("document.querySelector('.reader-error').contains(document.activeElement)"))
|