lang/node

Build a Website Accessibility Tester With JavaScript & Pa11y

C/H 2021. 11. 10. 01:44

Node.js 웹 접근성 테스트 도구

npm i express pa11y
# index.js
const pa11y = require('pa11y')

async function run() {
  const response = await pa11y('https://bluebreeze.co.kr')
  console.log(response)
}

run()
$ node index.js 
{
  documentTitle: 'Blue Breeze',
  pageUrl: 'https://bluebreeze.co.kr/',
  issues: [
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.InputText.Name',
      type: 'error',
      typeCode: 1,
      message: 'This textinput element does not have a name available to an accessibility API. Valid names are: label element, title undefined, aria-label undefined, aria-labelledby undefined.',
      context: `<input class="searchInput" type="text" name="search" value="" placeholder="Search..." onkeypress="if (event.keyCode == 13) { requestSearch('.util.use-top .searchInput') }">`,
      selector: '#wrap > header > div:nth-child(2) > div:nth-child(1) > div > div > input',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_3.1_3_1.F68',
      type: 'error',
      typeCode: 1,
      message: 'This form field should be labelled in some way. Use the label element (either with a "for" attribute or wrapped around the form field), or "title", "aria-label" or "aria-labelledby" attributes as appropriate.',
      context: `<input class="searchInput" type="text" name="search" value="" placeholder="Search..." onkeypress="if (event.keyCode == 13) { requestSearch('.util.use-top .searchInput') }">`,
      selector: '#wrap > header > div:nth-child(2) > div:nth-child(1) > div > div > input',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.Button.Name',
      type: 'error',
      typeCode: 1,
      message: 'This button element does not have a name available to an accessibility API. Valid names are: title undefined, element content, aria-label undefined, aria-labelledby undefined.',
      context: '<button type="button" class="button-menu">\n' +
        '              <svg xmlns="//ww...</button>',
      selector: '#wrap > header > div:nth-child(2) > div:nth-child(2) > button',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
      type: 'error',
      typeCode: 1,
      message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
      context: '<iframe id="aswift_0" name="aswift_0" style="left:0;position:absolute;top:0;border:0;width:1020px;height:280px;" sandbox="allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation-by-user-activation"...',
      selector: '#aswift_0',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId',
      type: 'error',
      typeCode: 1,
      message: 'Anchor element found with no link content and no name and/or ID attribute.',
      context: '<a class="link_page link_prev no-more-prev"><span class="xi-angle-left-min"...</a>',
      selector: '#container > main > div > div:nth-child(2) > div:nth-child(1) > a:nth-child(1)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent',
      type: 'error',
      typeCode: 1,
      message: 'Anchor element found with a valid href attribute, but no link content has been supplied.',
      context: '<a href="?page=2" class="link_page link_next "><span class="xi-angle-right-min...</a>',
      selector: '#container > main > div > div:nth-child(2) > div:nth-child(1) > a:nth-child(3)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID',
      type: 'error',
      typeCode: 1,
      message: 'This link points to a named anchor "page-more" within the document, but no anchor exists with that name.',
      context: '<a href="#page-more" class="paging-more">더보기</a>',
      selector: '#container > main > div > div:nth-child(2) > div:nth-child(2) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
      type: 'error',
      typeCode: 1,
      message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
      context: '<iframe id="aswift_1" name="aswift_1" style="left:0;position:absolute;top:0;border:0;width:300px;height:600px;" sandbox="allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation-by-user-activation" ...',
      selector: '#aswift_1',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId',
      type: 'error',
      typeCode: 1,
      message: 'Anchor element found with no link content and no name and/or ID attribute.',
      context: '<a class="twitter-timeline twitter-timeline-error" href="" data-twitter-extracted-i1636474687336287585="true"></a>',
      selector: '#container > main > aside > div:nth-child(8) > div:nth-child(4) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.InputText.Name',
      type: 'error',
      typeCode: 1,
      message: 'This textinput element does not have a name available to an accessibility API. Valid names are: label element, title undefined, aria-label undefined, aria-labelledby undefined.',
      context: `<input class="searchInput" type="text" name="search" value="" placeholder="검색내용을 입력하세요." onkeypress="if (event.keyCode == 13) { requestSearch('.util.use-sidebar .searchInput') }">`,
      selector: '#container > main > aside > div:nth-child(12) > div > input',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_3.1_3_1.F68',
      type: 'error',
      typeCode: 1,
      message: 'This form field should be labelled in some way. Use the label element (either with a "for" attribute or wrapped around the form field), or "title", "aria-label" or "aria-labelledby" attributes as appropriate.',
      context: `<input class="searchInput" type="text" name="search" value="" placeholder="검색내용을 입력하세요." onkeypress="if (event.keyCode == 13) { requestSearch('.util.use-sidebar .searchInput') }">`,
      selector: '#container > main > aside > div:nth-child(12) > div > input',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="http://korea-sw-eng.blogspot.com/" target="_blank">소프트웨어 공학 포털</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(1) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="http://www.emb-lab.com/wordpress/" target="_blank">EMB</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(2) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="http://vimawesome.com/" target="_blank">Vim Awesome</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(3) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="https://vim.rtorr.com/lang/ko/" target="_blank">Vim Cheat Sheet - ko</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(4) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="https://github.com/uncaose/straight-ci-layout" target="_blank">straight-ci-layout</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(5) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="https://github.com/uncaose/straight-ci-bbs" target="_blank">straight-ci-bbs</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(6) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="http://cikorea.net/" target="_blank">CodeIgniter 한국사용자포럼</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(7) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="https://opengirok.tistory.com" target="_blank">투명사회를 위한 정보공개센터</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(8) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="http://okmindmap.com" target="_blank">OK 마인드</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(9) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="https://riptutorial.com/ko/dart" target="_blank">Dart Tutorial</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(10) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail',
      type: 'error',
      typeCode: 1,
      message: 'This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.85:1. Recommendation:  change text colour to #767676.',
      context: '<a href="https://www.tta.or.kr" target="_blank">한국정보통신기술협회</a>',
      selector: '#footer > div > div:nth-child(3) > ul > li:nth-child(11) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
      type: 'error',
      typeCode: 1,
      message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
      context: '<iframe style="position:absolute;width:1px;height:1px;left:-100px;top:-100px" src="//uncaose.tistory.com/api" id="editEntry"></iframe>',
      selector: '#editEntry',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID',
      type: 'error',
      typeCode: 1,
      message: 'This link points to a named anchor "none" within the document, but no anchor exists with that name.',
      context: '<a href="#none" class="btn_mark" data-service="facebook"><span class="ico_sns ico_fb"></...</a>',
      selector: '#tistorySnsLayer > div > a:nth-child(1)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID',
      type: 'error',
      typeCode: 1,
      message: 'This link points to a named anchor "none" within the document, but no anchor exists with that name.',
      context: '<a href="#none" class="btn_mark" data-service="kakaotalk"><span class="ico_sns ico_kt"></...</a>',
      selector: '#tistorySnsLayer > div > a:nth-child(2)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID',
      type: 'error',
      typeCode: 1,
      message: 'This link points to a named anchor "none" within the document, but no anchor exists with that name.',
      context: '<a href="#none" class="btn_mark" data-service="kakaostory"><span class="ico_sns ico_ks"></...</a>',
      selector: '#tistorySnsLayer > div > a:nth-child(3)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID',
      type: 'error',
      typeCode: 1,
      message: 'This link points to a named anchor "none" within the document, but no anchor exists with that name.',
      context: '<a href="#none" class="btn_mark" data-service="twitter"><span class="ico_sns ico_tw"></...</a>',
      selector: '#tistorySnsLayer > div > a:nth-child(4)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID',
      type: 'error',
      typeCode: 1,
      message: 'This link points to a named anchor "none" within the document, but no anchor exists with that name.',
      context: '<a href="#none" class="btn_mark" data-service="url"><span class="ico_sns ico_url"><...</a>',
      selector: '#tistorySnsLayer > div > a:nth-child(5)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle1.Guideline1_1.1_1_1.H37',
      type: 'error',
      typeCode: 1,
      message: 'Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.',
      context: '<img class="lb-image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==">',
      selector: '#lightbox > div:nth-child(1) > div > img',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId',
      type: 'error',
      typeCode: 1,
      message: 'Anchor element found with no link content and no name and/or ID attribute.',
      context: '<a class="lb-prev" href=""></a>',
      selector: '#lightbox > div:nth-child(1) > div > div:nth-child(2) > a:nth-child(1)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId',
      type: 'error',
      typeCode: 1,
      message: 'Anchor element found with no link content and no name and/or ID attribute.',
      context: '<a class="lb-next" href=""></a>',
      selector: '#lightbox > div:nth-child(1) > div > div:nth-child(2) > a:nth-child(2)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId',
      type: 'error',
      typeCode: 1,
      message: 'Anchor element found with no link content and no name and/or ID attribute.',
      context: '<a class="lb-cancel"></a>',
      selector: '#lightbox > div:nth-child(1) > div > div:nth-child(3) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId',
      type: 'error',
      typeCode: 1,
      message: 'Anchor element found with no link content and no name and/or ID attribute.',
      context: '<a class="lb-close"></a>',
      selector: '#lightbox > div:nth-child(2) > div > div:nth-child(2) > a',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
      type: 'error',
      typeCode: 1,
      message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
      context: '<iframe id="aswift_3" name="aswift_3" style="left:0;position:absolute;top:0;border:0;width:undefinedpx;height:undefinedpx;" sandbox="allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation-by-user-...',
      selector: '#aswift_3',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
      type: 'error',
      typeCode: 1,
      message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
      context: '<iframe src="https://www.google.com/recaptcha/api2/aframe" width="0" height="0" style="display: none;"></iframe>',
      selector: '#tt-body-index > iframe:nth-child(32)',
      runner: 'htmlcs',
      runnerExtras: {}
    },
    {
      code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
      type: 'error',
      typeCode: 1,
      message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
      context: '<iframe id="google_esf" name="google_esf" src="https://googleads.g.doubleclick.net/pagead/html/r20211103/r20190131/zrt_lookup.html" style="display: none;"></iframe>',
      selector: '#google_esf',
      runner: 'htmlcs',
      runnerExtras: {}
    }
  ]
}
반응형