├── test ├── configs │ ├── empty.js │ ├── syntaxError.js │ ├── viewportWidth.js │ ├── simple.js │ └── skipAndOnly.js ├── runnerSpec │ └── a11y.js ├── windowSpec.js ├── commandLineArgsSpec.js ├── minimumTextSizeStandardSpec.js ├── sectionsSpec.js ├── standardsSpec.js ├── xpathSpec.js ├── a11ySpec.js └── iframeSpec.js ├── cucumber ├── mocha ├── cucumber.js ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── config.yml ├── DISCUSSION_TEMPLATE │ ├── help.yml │ └── feature-request.yml ├── SECURITY.md ├── workflows │ ├── test.yml │ ├── stale.yml │ ├── lock-threads.yml │ └── codeql.yml ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── features ├── support │ ├── timeouts.js │ ├── nullReporter.js │ ├── parameter_types.js │ ├── nullWindowAdapter.js │ └── helpers.js ├── README.md ├── cli │ ├── interactive_mode.feature │ ├── specify_url.feature │ ├── missing_config_file_warning.feature │ ├── specify_url_via_config.feature │ ├── exit_status.feature │ ├── specify_path_to_config_file.feature │ ├── coloured_output.feature │ ├── json_reporter.feature │ ├── command_line_help.feature │ ├── skipping_standards.feature │ ├── report_configuration_errors.feature │ ├── display_result_summary.feature │ ├── custom_reporter.feature │ ├── viewport_widths.feature │ └── display_failing_result.feature ├── ignore_x_frame_options.feature ├── standards │ └── mag │ │ ├── audio_and_video │ │ ├── 03_metadata.feature │ │ ├── 05_audio_conflict.feature │ │ ├── 04_volume_control.feature │ │ ├── 02_autoplay.feature │ │ └── 01_alternatives_for_audio_and_visual_content.feature │ │ ├── focus │ │ ├── 03_content_order.feature │ │ ├── 06_alternative_input_methods.feature │ │ ├── 02_keyboard_trap.feature │ │ ├── 05_user_interactions.feature │ │ └── 04_focus_order.feature │ │ ├── scripts_and_dynamic_content │ │ ├── 03_page_refreshes.feature │ │ ├── 04_timeouts.feature │ │ ├── 05_input_control.feature │ │ ├── 02_controlling_media.feature │ │ └── 01_progressive_functionality.feature │ │ ├── design │ │ ├── 05_spacing.feature │ │ ├── 03_styling_and_readability.feature │ │ ├── 12_flicker.feature │ │ ├── 10_choice.feature │ │ ├── 04_touch_target_size.feature │ │ ├── 09_consistency.feature │ │ ├── 07_actionable_elements.feature │ │ ├── 11_adjustability.feature │ │ ├── 01_colour_contrast.feature │ │ └── 02_colour_and_meaning.feature │ │ ├── text_equivalents │ │ ├── 02_decorative_content.feature │ │ └── 04_roles_traits_and_properties.feature │ │ ├── links │ │ ├── 02_links_to_alternative_formats.feature │ │ ├── 03_combining_repeated_links.feature │ │ └── 01_descriptive_links.feature │ │ ├── notifications │ │ ├── 04_feedback_and_assistance.feature │ │ ├── 02_standard_operating_system_notifications.feature │ │ ├── 03_error_messages_and_correction.feature │ │ └── 01_inclusive_notifications.feature │ │ ├── forms │ │ ├── 02_form_inputs.feature │ │ ├── 03_form_layout.feature │ │ └── 04_grouping_form_elements.feature │ │ ├── images │ │ ├── 01_images_of_text.feature │ │ └── 02_background_images.feature │ │ ├── editorial │ │ ├── 01_consistent_labelling.feature │ │ └── 03_instructions.feature │ │ └── structure │ │ ├── 04_grouped_elements.feature │ │ └── 03_containers_and_landmarks.feature ├── disabled_caching.feature ├── setting_up_the_browser_window.feature ├── setting_cookies.feature ├── hiding_warnings.feature └── hiding_errors.feature ├── electron-mocha.config.json ├── .gitignore ├── lib ├── electron │ ├── storage.js │ └── cookies.js ├── standards │ ├── tests │ │ ├── contentOrderMustBeLogical.js │ │ ├── linksMustHaveUnderlinesAndPointers.js │ │ ├── thereMustNotBeAKeyboardTrap.js │ │ ├── interactiveElementsMustBeFocusable.js │ │ ├── textLinksMustHaveMouseOverStateChange.js │ │ ├── consistentLabellingShouldBeUsed.js │ │ ├── imagesOfTextShouldBeAvoided.js │ │ ├── pageTitlesMustBeUniquelyAndClearlyIdentifiable.js │ │ ├── clearErrorMessagesMustBeProvided.js │ │ ├── userExperienceShouldBeConsistent.js │ │ ├── containersShouldBeUsedToDescribePageStructure.js │ │ ├── corePurposeMustBeDefined.js │ │ ├── timedResponsesMustBeAdjustable.js │ │ ├── actionableContentMustBeNavigableInAMeaningfulSequence.js │ │ ├── anchorsMustHaveHrefs.js │ │ ├── htmlMustHaveLangAttribute.js │ │ ├── interactionInputControlShouldBeAdaptable.js │ │ ├── alternativeInputMethodsMustBeSupported.js │ │ ├── imagesMustHaveAltAttributes.js │ │ ├── actionsMustBeTriggeredWhenAppropriate.js │ │ ├── relevantMetadataShouldBeProvidedForAllMedia.js │ │ ├── focusedElementsMustVisiblyChangeState.js │ │ ├── changesToLanguageMustBeIndicated.js │ │ ├── focusedElementsMustBeIdentifiable.js │ │ ├── interfacesMustProvideMultipleWaysToInteractWithContent.js │ │ ├── automaticPageRefreshesMustNotBeUsedWithoutWarning.js │ │ ├── touchTargetsMustBeLargeEnoughToTouchAccurately.js │ │ ├── aDefaultInputFormatMustBeIndicatedAndSupported.js │ │ ├── visualFormattingAloneMustNotBeUsedToConveyMeaning.js │ │ ├── repeatedLinksToTheSameResourceMustBeCombined.js │ │ ├── additionalInstructionsShouldBeProvided.js │ │ ├── contentMustNotFlickerOrFlash.js │ │ ├── decorativeImagesMustBeHiddenFromAssistiveTechnology.js │ │ ├── notificationsMustBeBothVisibleAndAudible.js │ │ ├── tooltipsMustNotRepeatLinkTextOrOtherAlternatives.js │ │ ├── elementsMustHaveAccessibilityPropertiesSetAppropriately.js │ │ ├── inactiveSpaceShouldBeProvidedAroundActionableElements.js │ │ ├── volumeControlsShouldBeProvidedForInteractiveMedia.js │ │ ├── controlsLabelsAndOtherFormElementsMustBeProperlyGrouped.js │ │ ├── labelsMustBeCloseAndLaidOutAppropriately.js │ │ ├── scriptsAndDynamicContentMustBeBuiltInAProgressiveManner.js │ │ ├── allDocumentsMustHaveAW3cRecommendedDoctype.js │ │ ├── preferStandardOperatingSystemNotifications.js │ │ ├── focusOrContextMustNotAutomaticallyChangeDuringUserInput.js │ │ ├── linkAndNavigationTextMustUniquelyDescribeTheTargetOrFunction.js │ │ ├── nonCriticalFeedbackOrAssistanceShouldBeProvidedWhenAppropriate.js │ │ ├── audioMustNotPlayAutomaticallyWithoutControls.js │ │ ├── narrativeAudioShouldNotConflictWithAssistiveTechnology.js │ │ ├── linksToAlternativeFormatsMustIndicateThatAnAlternativeIsOpening.js │ │ ├── mediaThatUpdatesAndAnimationMustHaveAPauseStopOrHideControl.js │ │ ├── interactiveMediaShouldBeAdjustableForUserAbilityAndPreference.js │ │ ├── alternativeDeliveryForEmbeddedMediaMustBeProvided.js │ │ ├── colourCombinationsMustPassColourContrastCheck.js │ │ ├── alternativesMustBrieflyDescribeEditorialIntent.js │ │ ├── meaningfulBackgroundImagesMustHaveAccessibleAlternatives.js │ │ ├── titleAttributesMustNotDuplicateContent.js │ │ ├── groupedInterfaceElementsMustBeRepresentedAsASingleComponent.js │ │ ├── titleAttributesOnlyOnInputs.js │ │ ├── exactlyOneMainHeading.js │ │ ├── editorialLinksMustBeSelfEvident.js │ │ ├── informationConveyedWithColourMustAlsoBeIdentifiableFromContextOrMarkup.js │ │ ├── exactlyOneMainLandmark.js │ │ ├── elementsWithZeroTabIndexMustBeFocusableByDefault.js │ │ ├── formsMustHaveSubmitButtons.js │ │ ├── useTablesForData.js │ │ ├── elementsMustBeVisibleOnFocus.js │ │ ├── textCannotBeTooSmall.js │ │ ├── titleElementMustIdentifyMainContent.js │ │ ├── headingsMustBeInAscendingOrder.js │ │ ├── contentMustBeVisibleAndUsableWithPageZoomed.js │ │ ├── fieldsMustHaveLabelsOrTitles.js │ │ ├── coreContentMustBeAccessibleWhenStylingIsRemoved.js │ │ ├── documentMustNotRequireJavaScriptOrCssToFunction.js │ │ ├── support │ │ │ └── detectTableType.js │ │ ├── contentMustBeVisibleAndUsableWithTextResized.js │ │ ├── markupMustValidateAgainstDoctype.js │ │ ├── textMustBeStyledWithUnitsThatAreResizableInAllBrowsers.js │ │ └── contentMustFollowHeadings.js │ └── index.js ├── a11y.js ├── reporter.js ├── cli │ └── args.js ├── config │ └── loader.js ├── reporters │ └── json.js └── results │ ├── xpath.js │ └── standardResult.js ├── a11y.js ├── guides ├── contributing │ ├── suggesting-improvements.md │ ├── raising-issues-with-standards-checks.md │ └── code-changes.md └── using │ ├── semi-automated-tests.md │ └── checking-a-website.md ├── electron ├── index.html ├── windowAdapter.js ├── mainWindow.css ├── answerFrame.css ├── renderer.js └── bbc-a11y.js ├── docker ├── Dockerfile └── README.md ├── scripts └── generate-coverage ├── xvfb ├── bin └── bbc-a11y.js ├── package.json └── README.md /test/configs/empty.js: -------------------------------------------------------------------------------- 1 | /* empty config */ 2 | -------------------------------------------------------------------------------- /cucumber: -------------------------------------------------------------------------------- 1 | ./node_modules/.bin/cucumber-electron "$@" 2 | -------------------------------------------------------------------------------- /mocha: -------------------------------------------------------------------------------- 1 | node_modules/.bin/electron-mocha --renderer "$@" 2 | -------------------------------------------------------------------------------- /test/configs/syntaxError.js: -------------------------------------------------------------------------------- 1 | /* syntax error... */ 2 | -> e 3 | -------------------------------------------------------------------------------- /test/configs/viewportWidth.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | page('https://www.bbc.co.uk', { 3 | width: 789 4 | }) 5 | -------------------------------------------------------------------------------- /cucumber.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: '--tags \'not @keep-ansi-escape-sequences and not @todo\'' 3 | } 4 | -------------------------------------------------------------------------------- /test/configs/simple.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | page('https://www.bbc.co.uk') 3 | page('https://www.bbc.co.uk/news') 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # All changes should be reviewed by codeowners 2 | * @bbc/open-dev @bbc/audience-facing-accessibility -------------------------------------------------------------------------------- /features/support/timeouts.js: -------------------------------------------------------------------------------- 1 | const { setDefaultTimeout } = require('@cucumber/cucumber') 2 | 3 | setDefaultTimeout(10000) 4 | -------------------------------------------------------------------------------- /electron-mocha.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "webPreferences": { 3 | "webSecurity": false, 4 | "enableRemoteModule": true 5 | } 6 | } -------------------------------------------------------------------------------- /test/runnerSpec/a11y.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | page('https://a11ytests.com/perfect') 3 | page('https://a11ytests.com/perfect') 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | Gemfile.lock 3 | pkg 4 | tags 5 | .DS_Store 6 | /a11y.js 7 | node_modules 8 | lib/bundle.js 9 | .idea 10 | yarn.lock 11 | dist 12 | -------------------------------------------------------------------------------- /test/windowSpec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const assert = require('assert') 3 | 4 | describe('window', () => { 5 | it('should exist', () => { 6 | assert(window) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /features/README.md: -------------------------------------------------------------------------------- 1 | These are the acceptance tests for the a11y tool itself. 2 | 3 | To read the accessibility standards checks that this tool will run against your app, 4 | you need to look in the `standards` directory. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Requests & Questions 4 | url: https://github.com/bbc/sqs-consumer/discussions 5 | about: Please ask and answer questions here. -------------------------------------------------------------------------------- /lib/electron/storage.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clear: function () { 3 | const electronRemote = require('electron').remote 4 | 5 | const session = electronRemote.session.defaultSession 6 | return session.clearStorageData() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /features/cli/interactive_mode.feature: -------------------------------------------------------------------------------- 1 | Feature: Interactive Mode 2 | 3 | Scenario: Running a11y interactively 4 | Given a website running on a11ytests.com 5 | When I run `bbc-a11y https://a11ytests.com/perfect --interactive` 6 | And the window should remain open 7 | -------------------------------------------------------------------------------- /features/ignore_x_frame_options.feature: -------------------------------------------------------------------------------- 1 | Feature: Ignore x-frame-options header 2 | 3 | Scenario: Page sets x-frame-options=SAMEORIGIN 4 | Given a website running on a11ytests.com 5 | When I run `bbc-a11y https://a11ytests.com/x-frame-options` 6 | Then it passes 7 | -------------------------------------------------------------------------------- /lib/standards/tests/contentOrderMustBeLogical.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Content order must be logical', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page with illogical content order', 7 | 8 | manualTest: { 9 | question: 'Is content logically ordered?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /a11y.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | page('https://www.bbc.co.uk', { 3 | skip: [ 4 | 'Headings: Content must follow headings', 5 | 'Minimum Text Size: Text cannot be too small' 6 | ] 7 | }) 8 | page('https://www.bbc.co.uk/news', { 9 | skip: 'Headings: Content must follow headings' 10 | }) 11 | -------------------------------------------------------------------------------- /test/configs/skipAndOnly.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | page('https://www.bbc.co.uk', { 3 | skip: 'x' 4 | }) 5 | page('https://www.bbc.co.uk/news', { 6 | skip: ['y', 'z'] 7 | }) 8 | page('https://www.bbc.co.uk/sport', { 9 | only: 'a' 10 | }) 11 | page('https://www.bbc.co.uk/weather', { 12 | only: ['b', 'c'] 13 | }) 14 | -------------------------------------------------------------------------------- /guides/contributing/suggesting-improvements.md: -------------------------------------------------------------------------------- 1 | # Suggesting improvements to bbc-a11y 2 | 3 | If you believe there is a bug in bbc-a11y, please [submit an issue](https://github.com/bbc/bbc-a11y/issues/new) 4 | with as much relevant detail as possible. There is a template on the issue page 5 | that should help you describe the problem. 6 | -------------------------------------------------------------------------------- /features/cli/specify_url.feature: -------------------------------------------------------------------------------- 1 | Feature: Specify URL 2 | 3 | Scenario: No config, just pass page URL on command-line 4 | Given a website running on a11ytests.com 5 | When I run `bbc-a11y https://a11ytests.com/perfect` 6 | Then it should pass with: 7 | """ 8 | ✓ https://a11ytests.com/perfect 9 | """ 10 | -------------------------------------------------------------------------------- /lib/standards/tests/linksMustHaveUnderlinesAndPointers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Links must have underlines and pointers', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page that links with no underlines or pointers', 7 | 8 | manualTest: { 9 | question: 'Do all links have underlines and pointers?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /features/support/nullReporter.js: -------------------------------------------------------------------------------- 1 | module.exports = class NullReporter { 2 | runStarted () { 3 | this.results = {} 4 | } 5 | 6 | unexpectedError (e) { 7 | console.log(e) 8 | throw e 9 | } 10 | 11 | pageChecked (page, results) { 12 | this.results[page.url] = results 13 | } 14 | 15 | runEnded () { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/standards/tests/thereMustNotBeAKeyboardTrap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'There must not be a keyboard trap', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page that has a keyboard trap', 7 | 8 | manualTest: { 9 | question: 'Is anything a keyboard trap?', 10 | passText: 'No', 11 | failText: 'Yes' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/standards/tests/interactiveElementsMustBeFocusable.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Interactive elements must be focusable', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page with interactive elements that are not focusable', 7 | 8 | manualTest: { 9 | question: 'Are all (and only) interactive elements focusable?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/standards/tests/textLinksMustHaveMouseOverStateChange.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Text links must have mouse over state change', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'text link that has no mouse over state change', 7 | 8 | manualTest: { 9 | question: 'Do all text links have a mouse over state change?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/standards/tests/consistentLabellingShouldBeUsed.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Consistent labelling should be used', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page with inconsistent labelling', 7 | 8 | manualTest: { 9 | question: 'Are consistent editorial labels used?', 10 | passText: 'Yes (or not applicable)' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/standards/tests/imagesOfTextShouldBeAvoided.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Images of text should be avoided', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page with unnecessary images of text', 7 | 8 | manualTest: { 9 | question: 'Are there any unnecessary images of text?', 10 | passText: 'No', 11 | failText: 'Yes' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/standards/tests/pageTitlesMustBeUniquelyAndClearlyIdentifiable.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Page titles must be uniquely and clearly identifiable', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page title that is not uniquely and clearly identifiable', 7 | 8 | manualTest: { 9 | question: 'Is the page title uniquely and clearly identifiable?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /features/cli/missing_config_file_warning.feature: -------------------------------------------------------------------------------- 1 | Feature: Missing configuration file warning 2 | 3 | Scenario: Running the tool with no arguments and no config file 4 | When I run `bbc-a11y` 5 | Then it should fail with: 6 | """ 7 | Missing configuration file (a11y.js). 8 | 9 | Please visit http://github.com/bbc/bbc-a11y for more information. 10 | """ 11 | -------------------------------------------------------------------------------- /lib/standards/tests/clearErrorMessagesMustBeProvided.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Clear error messages must be provided', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page that provides unclear error messages', 7 | 8 | manualTest: { 9 | question: 'Are clear accessible error messages provided when needed?', 10 | passText: 'Yes (or not applicable)' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/standards/tests/userExperienceShouldBeConsistent.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'User experience should be consistent', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page that provides inconsitent user experience', 7 | 8 | manualTest: { 9 | question: 'Is the user experience consistent across pages and screens, eg. layout, labels, focus states etc.?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/standards/tests/containersShouldBeUsedToDescribePageStructure.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Containers should be used to describe page structure', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page that does not use containers to describe page structure', 7 | 8 | manualTest: { 9 | question: 'Are suitable containers used to help describe page structure?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/standards/tests/corePurposeMustBeDefined.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Core purpose must be defined', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page whose core purpose (to inform, educate, or entertain) is not clearly defined', 7 | 8 | manualTest: { 9 | question: 'Is the core purpose (to inform, educate, or entertain) of the page clearly defined?' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/standards/tests/timedResponsesMustBeAdjustable.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Timed responses must be adjustable', 3 | 4 | type: 'manual', 5 | 6 | failsForEach: 'page with timed responses that are not adjustable', 7 | 8 | manualTest: { 9 | question: 'Are there any timed responses that cannot be adjusted?', 10 | passText: 'No', 11 | failText: 'Yes' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /electron/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Some content
Second level second level anchor