Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade browser/accessibility tests for Puppeteer v18 #2532

Merged
merged 9 commits into from
Jan 17, 2023
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ module.exports = {
files: ['**/*.test.{cjs,js,mjs}'],
env: {
jest: true
},
globals: {
page: true,
browser: true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows the jest-puppeteer page and browser globals to work without lint errors

}
}
]
Expand Down
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ updates:
- dependency-type: direct

ignore:
# Ignore major updates (@axe-core/puppeteer peer puppeteer <= 18)
- dependency-name: puppeteer
update-types: ['version-update:semver-major']

# Ignore major/minor updates (Marked parser changes output)
- dependency-name: jstransformer-marked
update-types: ['version-update:semver-major', 'version-update:semver-minor']
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
run: npm run build

- name: Lint and test
run: npm test -- --runInBand
run: npm test -- --color --maxWorkers=2
domoscargin marked this conversation as resolved.
Show resolved Hide resolved

# Share data between the build and deploy jobs so we don't need to run `npm run build` again on deploy
# Upload the deploy folder as an artifact so it can be downloaded and used in the deploy job
Expand All @@ -52,4 +52,3 @@ jobs:
name: build
path: deploy/**
retention-days: 1

Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
const { AxePuppeteer } = require('@axe-core/puppeteer')

const { AxePuppeteer } = require('axe-puppeteer')
const { goTo } = require('../lib/puppeteer-helpers.js')

const { setupPage } = require('../lib/jest-utilities.js')
const configPaths = require('../lib/paths.js')
const PORT = configPaths.testPort
async function analyze (page, path) {
await goTo(page, path)

let page
const baseUrl = 'http://localhost:' + PORT

async function audit (page) {
const axe = new AxePuppeteer(page)
.include('body')
// axe reports there is "no label associated with the text field", when there is one.
Expand All @@ -20,58 +16,47 @@ async function audit (page) {
.exclude('.govuk-skip-link')
// axe reports that the back to top button is not inside a landmark, which is intentional.
.exclude('.app-back-to-top')
// axe reports that the frame "does not have a main landmark" and example <h1> headings
// violate "Heading levels should only increase by one", which is intentional.
// https://github.com/alphagov/govuk-design-system/pull/2442#issuecomment-1326600528
.exclude('.app-example__frame')
domoscargin marked this conversation as resolved.
Show resolved Hide resolved

const results = await axe.analyze()

return results.violations
return axe.analyze()
}

beforeAll(async () => {
page = await setupPage()
})

afterAll(async () => {
await page.close()
})

describe('Accessibility Audit', () => {
describe('Home page - layout.njk', () => {
it('validates', async () => {
await page.goto(baseUrl + '/', { waitUntil: 'load' })
const violations = await audit(page)
expect(violations).toEqual([])
const results = await analyze(page, '/')
expect(results).toHaveNoViolations()
domoscargin marked this conversation as resolved.
Show resolved Hide resolved
})
})

describe('Component page - layout-pane.njk', () => {
it('validates', async () => {
await page.goto(baseUrl + '/components/radios/', { waitUntil: 'load' })
const violations = await audit(page)
expect(violations).toEqual([])
const results = await analyze(page, '/components/radios/')
expect(results).toHaveNoViolations()
})
})

describe('Patterns page - layout-pane.njk', () => {
it('validates', async () => {
await page.goto(baseUrl + '/patterns/gender-or-sex/', { waitUntil: 'load' })
const violations = await audit(page)
expect(violations).toEqual([])
const results = await analyze(page, '/patterns/gender-or-sex/')
expect(results).toHaveNoViolations()
})
})

describe('Get in touch page - layout-single-page.njk', () => {
it('validates', async () => {
await page.goto(baseUrl + '/get-in-touch/', { waitUntil: 'load' })
const violations = await audit(page)
expect(violations).toEqual([])
const results = await analyze(page, '/get-in-touch/')
expect(results).toHaveNoViolations()
})
})

describe('Site Map page - layout-sitemap.njk', () => {
it('validates', async () => {
await page.goto(baseUrl + '/sitemap/', { waitUntil: 'load' })
const violations = await audit(page)
expect(violations).toEqual([])
const results = await analyze(page, '/sitemap/')
expect(results).toHaveNoViolations()
})
})
})
79 changes: 47 additions & 32 deletions __tests__/back-to-top.test.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,63 @@

const { setupPage } = require('../lib/jest-utilities.js')
const configPaths = require('../lib/paths.js')
const PORT = configPaths.testPort
const { goTo, isVisible } = require('../lib/puppeteer-helpers.js')

let page
const baseUrl = 'http://localhost:' + PORT
describe('Back to top', () => {
let $module
let $backToTopLink
let pageHeight

beforeAll(async () => {
page = await setupPage()
})
async function setup (page) {
$module = await page.$('[data-module="app-back-to-top"]')
$backToTopLink = await $module.$('a')

afterAll(async () => {
await page.close()
})
// Scrollable height of body
pageHeight = await page.$eval('body', ($element) => $element.scrollHeight) ?? 0
}

const BACK_TO_TOP_LINK_SELECTOR = '[data-module="app-back-to-top"] a'
function scrollTo (page, scrollY) {
return page.evaluate((y) => window.scroll(0, y), scrollY)
}

beforeEach(async () => {
await page.setJavaScriptEnabled(true)

await goTo(page, '/styles/colour/')
await scrollTo(page, 0)
await setup(page)
})

describe('Back to top', () => {
it('is always visible when JavaScript is disabled', async () => {
await page.setJavaScriptEnabled(false)
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
const isBackToTopVisible = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true })
expect(isBackToTopVisible).toBeTruthy()

// Reload page again
await page.reload()
await setup(page)

// Visible on page
await expect(isVisible($backToTopLink)).resolves.toBe(true)
})

it('is hidden when at the top of the page', async () => {
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
const isBackToTopHidden = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: false })
expect(isBackToTopHidden).toBeTruthy()
await scrollTo(page, 0)

// Visible on page, hidden from viewport
await expect(isVisible($backToTopLink)).resolves.toBe(true)
await expect($backToTopLink.isIntersectingViewport()).resolves.toBe(false)
})

it('is visible when at the bottom of the page', async () => {
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
// Scroll to the bottom of the page
await page.evaluate(() => window.scrollBy(0, document.body.scrollHeight))
const isBackToTopVisible = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true })
expect(isBackToTopVisible).toBeTruthy()
await scrollTo(page, pageHeight)

// Visible on page, shown in viewport
await expect(isVisible($backToTopLink)).resolves.toBe(true)
await expect($backToTopLink.isIntersectingViewport()).resolves.toBe(true)
})

it('goes back to the top of the page when interacted with', async () => {
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
// Scroll to the bottom of the page
await page.evaluate(() => window.scrollBy(0, document.body.scrollHeight))
// Make sure the back to top component is available to click
await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true })
await page.click(BACK_TO_TOP_LINK_SELECTOR)
const isAtTopOfPage = await page.evaluate(() => window.scrollY === 0)
expect(isAtTopOfPage).toBeTruthy()
await scrollTo(page, pageHeight)
await $backToTopLink.click()

// Scrolled to top
await expect(page.evaluate(() => window.scrollY)).resolves.toBe(0)
})
})
37 changes: 13 additions & 24 deletions __tests__/component-options.test.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@

const { setupPage } = require('../lib/jest-utilities.js')
const configPaths = require('../lib/paths.js')
const PORT = configPaths.testPort

let page
const baseUrl = 'http://localhost:' + PORT

beforeEach(async () => {
page = await setupPage()
})

afterEach(async () => {
await page.close()
})
const { goTo } = require('../lib/puppeteer-helpers.js')

describe('Component page', () => {
it('should contain a "Nunjucks" tab heading', async () => {
await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' })
await goTo(page, '/components/back-link/')

const nunjucksTabHeadings = await page.evaluate(() => Array.from(document.querySelectorAll('.js-tabs__item a'))
.filter(element => element.textContent === 'Nunjucks'))
.filter(element => element.textContent === 'Nunjucks')
)

expect(nunjucksTabHeadings[0]).toBeTruthy()
})

it('"Nunjucks" tab content should contain a details summary with "Nunjucks macro options" text', async () => {
await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' })
await goTo(page, '/components/back-link/')

// Get "aria-controls" attributes from "Nunjucks" tab headings
const nunjucksTabHeadingControls = await page.evaluateHandle(() => Array.from(document.querySelectorAll('.js-tabs__item a'))
.filter(element => element.textContent === 'Nunjucks')
.map(element => element.getAttribute('aria-controls')))
.map(element => element.getAttribute('aria-controls'))
)

const tabContentIds = await nunjucksTabHeadingControls.jsonValue() // Returns Puppeteer JSONHandle

Expand All @@ -44,7 +33,7 @@ describe('Component page', () => {
})

it('"Nunjucks" tab content should contain a details element that has a table with "Name", "Type" and "Description" column headings', async () => {
await page.goto(baseUrl + '/components/back-link/', { waitUntil: 'load' })
await goTo(page, '/components/back-link/')

// Get "aria-controls" attributes from "Nunjucks" tab headings
const nunjucksTabHeadingControls = await page.evaluateHandle(() => Array.from(document.querySelectorAll('.js-tabs__item a'))
Expand All @@ -62,24 +51,24 @@ describe('Component page', () => {
})

it('macro options should be opened and in view when linked to', async () => {
await page.goto(baseUrl + '/components/back-link/#options-back-link-example', { waitUntil: 'load' })
await goTo(page, '/components/back-link/#options-back-link-example')

// Check if example's macro options details element is open
await page.waitForSelector('#options-back-link-example-details[open=open]')

// Check if the example has been scrolled into the viewport
const $example = await page.$('#options-back-link-example')
expect(await $example.isIntersectingViewport()).toBe(true)
const $example = await page.$('#options-back-link-example-details')
domoscargin marked this conversation as resolved.
Show resolved Hide resolved
await expect($example.isIntersectingViewport()).resolves.toBe(true)
})

it('macro options subtable should be opened and in view when linked to', async () => {
await page.goto(baseUrl + '/components/text-input/#options-text-input-example--label', { waitUntil: 'load' })
await goTo(page, '/components/text-input/#options-text-input-example--label')

// Check if example's macro options details element is open
await page.waitForSelector('#options-text-input-example-details[open=open]')

// Check if the example has been scrolled into the viewport
const $example = await page.$('#options-text-input-example--label')
expect(await $example.isIntersectingViewport()).toBe(true)
await expect($example.isIntersectingViewport()).resolves.toBe(true)
})
})
Loading