Download tests/test_reader_accessibility_browser.py from VoiceOfML/Search: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/VoiceOfML/Search/resolve/main/tests/test_reader_accessibility_browser.py
- Command line
-
hf download hf://spaces/VoiceOfML/Search/tests/test_reader_accessibility_browser.py
-
curl -L -o test_reader_accessibility_browser.py https://huggingface.co/spaces/VoiceOfML/Search/resolve/main/tests/test_reader_accessibility_browser.py
11.8 kB
| """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)")) | |