From a1fe2ca632d6fbc3580e1c53e34bed2812f1cfe2 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Sat, 1 Oct 2022 21:42:08 +0100 Subject: [PATCH 1/9] Ensure `renderAndInitialise()` uses scoped page object --- lib/puppeteer-helpers.js | 28 +++++++++++++--- src/govuk/components/button/button.test.js | 21 ++++-------- .../character-count/character-count.test.js | 33 ++++++++----------- .../error-summary/error-summary.test.js | 15 ++------- .../notification-banner.test.js | 21 +++--------- 5 files changed, 50 insertions(+), 68 deletions(-) diff --git a/lib/puppeteer-helpers.js b/lib/puppeteer-helpers.js index b99bfab2f8..abf8520ba4 100644 --- a/lib/puppeteer-helpers.js +++ b/lib/puppeteer-helpers.js @@ -1,6 +1,9 @@ const { componentNameToJavaScriptClassName } = require('./helper-functions.js') const { renderHtml } = require('./jest-helpers.js') +const configPaths = require('../config/paths.js') +const PORT = configPaths.ports.test + /** * Render and initialise a component within test boilerplate HTML * @@ -13,9 +16,9 @@ const { renderHtml } = require('./jest-helpers.js') * (which lets you instantiate it a different way, like using `initAll`, * or run arbitrary code) * + * @param {import('puppeteer').Page} page - Puppeteer page object * @param {String} componentName - The kebab-cased name of the component * @param {Object} options - * @param {String} options.baseUrl - The base URL of the test server * @param {Object} options.nunjucksParams - Params passed to the Nunjucks macro * @param {Object} [options.javascriptConfig] - The configuration hash passed to * the component's class for initialisation @@ -23,10 +26,10 @@ const { renderHtml } = require('./jest-helpers.js') * browser to execute arbitrary initialisation. Receives an object with the * passed configuration as `config` and the PascalCased component name as * `componentClassName` - * @returns {Promise} + * @returns {Promise} Puppeteer page object */ -async function renderAndInitialise (componentName, options = {}) { - await page.goto(`${options.baseUrl}/tests/boilerplate`, { waitUntil: 'load' }) +async function renderAndInitialise (page, componentName, options = {}) { + await goTo(page, '/tests/boilerplate') const html = renderHtml(componentName, options.nunjucksParams) @@ -51,6 +54,21 @@ async function renderAndInitialise (componentName, options = {}) { return page } +/** + * Navigate to path + * + * @param {import('puppeteer').Page} page - Puppeteer page object + * @param {URL['pathname']} path - URL path + * @returns {Promise} Puppeteer page object + */ +async function goTo (page, path) { + await page.goto(`http://localhost:${PORT}${path}`, { waitUntil: 'load' }) + await page.bringToFront() + + return page +} + module.exports = { - renderAndInitialise + renderAndInitialise, + goTo } diff --git a/src/govuk/components/button/button.test.js b/src/govuk/components/button/button.test.js index c0ebf4a5dd..548d27eccb 100644 --- a/src/govuk/components/button/button.test.js +++ b/src/govuk/components/button/button.test.js @@ -174,12 +174,8 @@ describe('/components/button', () => { }) describe('using JavaScript configuration', () => { - let page - - // To ensure beforeEach(async () => { - page = await renderAndInitialise('button', { - baseUrl, + await renderAndInitialise(page, 'button', { nunjucksParams: examples.default, javascriptConfig: { preventDoubleClick: true @@ -226,11 +222,8 @@ describe('/components/button', () => { }) describe('using JavaScript configuration, but cancelled by data-attributes', () => { - let page - - it('does not prevent multiple submissions', async () => { - page = await renderAndInitialise('button', { - baseUrl, + beforeEach(async () => { + await renderAndInitialise(page, 'button', { nunjucksParams: examples["don't prevent double click"], javascriptConfig: { preventDoubleClick: true @@ -238,7 +231,9 @@ describe('/components/button', () => { }) await trackClicks(page) + }) + it('does not prevent multiple submissions', async () => { await page.click('button') await page.click('button') @@ -249,12 +244,8 @@ describe('/components/button', () => { }) describe('using `initAll`', () => { - let page - - // To ensure beforeEach(async () => { - page = await renderAndInitialise('button', { - baseUrl, + await renderAndInitialise(page, 'button', { nunjucksParams: examples.default, initialiser () { window.GOVUKFrontend.initAll({ diff --git a/src/govuk/components/character-count/character-count.test.js b/src/govuk/components/character-count/character-count.test.js index 5f6c858bbb..7ff0340246 100644 --- a/src/govuk/components/character-count/character-count.test.js +++ b/src/govuk/components/character-count/character-count.test.js @@ -334,8 +334,7 @@ describe('Character count', () => { describe('at instantiation', () => { it('configures the number of characters', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['to configure in JavaScript'], javascriptConfig: { maxlength: 10 @@ -350,9 +349,9 @@ describe('Character count', () => { ) expect(message).toEqual('You have 1 character too many') }) + it('configures the number of words', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['to configure in JavaScript'], javascriptConfig: { maxwords: 10 @@ -367,9 +366,9 @@ describe('Character count', () => { ) expect(message).toEqual('You have 1 word too many') }) + it('configures the threshold', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['to configure in JavaScript'], javascriptConfig: { maxlength: 10, @@ -386,8 +385,7 @@ describe('Character count', () => { describe('via `initAll`', () => { it('configures the number of characters', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['to configure in JavaScript'], initialiser () { window.GOVUKFrontend.initAll({ @@ -408,8 +406,7 @@ describe('Character count', () => { }) it('configures the number of words', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['to configure in JavaScript'], initialiser () { window.GOVUKFrontend.initAll({ @@ -428,9 +425,9 @@ describe('Character count', () => { ) expect(message).toEqual('You have 1 word too many') }) + it('configures the threshold', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['to configure in JavaScript'], initialiser () { window.GOVUKFrontend.initAll({ @@ -454,8 +451,7 @@ describe('Character count', () => { describe('when data-attributes are present', () => { it('uses `maxlength` data attribute instead of the JS one', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples.default, javascriptConfig: { maxlength: 12 // JS configuration that would tell 1 character remaining @@ -472,8 +468,7 @@ describe('Character count', () => { }) it("uses `maxlength` data attribute instead of JS's `maxwords`", async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples.default, // Default example counts characters javascriptConfig: { maxwords: 12 @@ -490,8 +485,7 @@ describe('Character count', () => { }) it('uses `maxwords` data attribute instead of the JS one', async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['with word count'], javascriptConfig: { maxwords: 12 // JS configuration that would tell 1 word remaining @@ -508,8 +502,7 @@ describe('Character count', () => { }) it("uses `maxwords` data attribute instead of the JS's `maxlength`", async () => { - await renderAndInitialise('character-count', { - baseUrl, + await renderAndInitialise(page, 'character-count', { nunjucksParams: examples['with word count'], javascriptConfig: { maxlength: 10 diff --git a/src/govuk/components/error-summary/error-summary.test.js b/src/govuk/components/error-summary/error-summary.test.js index b58c946233..a611b4faca 100644 --- a/src/govuk/components/error-summary/error-summary.test.js +++ b/src/govuk/components/error-summary/error-summary.test.js @@ -63,11 +63,8 @@ describe('Error Summary', () => { }) describe('using JavaScript configuration', () => { - let page - beforeAll(async () => { - page = await renderAndInitialise('error-summary', { - baseUrl, + await renderAndInitialise(page, 'error-summary', { nunjucksParams: examples.default, javascriptConfig: { disableAutoFocus: true @@ -108,11 +105,8 @@ describe('Error Summary', () => { }) describe('using JavaScript configuration, but enabled via data-attributes', () => { - let page - beforeAll(async () => { - page = await renderAndInitialise('error-summary', { - baseUrl, + await renderAndInitialise(page, 'error-summary', { nunjucksParams: examples['autofocus explicitly enabled'] }) }) @@ -133,11 +127,8 @@ describe('Error Summary', () => { }) describe('using `initAll`', () => { - let page - beforeAll(async () => { - page = await renderAndInitialise('error-summary', { - baseUrl, + await renderAndInitialise(page, 'error-summary', { nunjucksParams: examples.default, initialiser () { window.GOVUKFrontend.initAll({ diff --git a/src/govuk/components/notification-banner/notification-banner.test.js b/src/govuk/components/notification-banner/notification-banner.test.js index 01521db528..77fa9d1663 100644 --- a/src/govuk/components/notification-banner/notification-banner.test.js +++ b/src/govuk/components/notification-banner/notification-banner.test.js @@ -57,13 +57,9 @@ describe('Notification banner, when type is set to "success"', () => { }) describe('and auto-focus is disabled using JavaScript configuration', () => { - let page - beforeAll(async () => { - page = await renderAndInitialise('notification-banner', { - baseUrl, - nunjucksParams: - examples['with type as success'], + await renderAndInitialise(page, 'notification-banner', { + nunjucksParams: examples['with type as success'], javascriptConfig: { disableAutoFocus: true } @@ -84,13 +80,9 @@ describe('Notification banner, when type is set to "success"', () => { }) describe('and auto-focus is disabled using options passed to initAll', () => { - let page - beforeAll(async () => { - page = await renderAndInitialise('notification-banner', { - baseUrl, - nunjucksParams: - examples['with type as success'], + await renderAndInitialise(page, 'notification-banner', { + nunjucksParams: examples['with type as success'], initialiser () { window.GOVUKFrontend.initAll({ notificationBanner: { @@ -115,11 +107,8 @@ describe('Notification banner, when type is set to "success"', () => { }) describe('and autofocus is disabled in JS but enabled in data attributes', () => { - let page - beforeAll(async () => { - page = await renderAndInitialise('notification-banner', { - baseUrl, + await renderAndInitialise(page, 'notification-banner', { nunjucksParams: examples['auto-focus explicitly enabled, with type as success'], javascriptConfig: { disableAutoFocus: true From 7d825d4663de17fd136f81b44be9411c980356c2 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Wed, 5 Oct 2022 14:56:03 +0100 Subject: [PATCH 2/9] Add new `goTo()` and `goToComponent()` helpers --- lib/puppeteer-helpers.js | 24 +++++- src/govuk/all.test.js | 13 +-- .../components/accordion/accordion.test.js | 70 ++++++++++------ src/govuk/components/button/button.test.js | 62 ++++---------- .../character-count/character-count.test.js | 83 +++++++++++-------- .../components/checkboxes/checkboxes.test.js | 69 ++++++++++----- src/govuk/components/details/details.test.js | 5 +- .../error-summary/error-summary.test.js | 18 ++-- src/govuk/components/header/header.test.js | 33 ++++---- .../notification-banner.test.js | 41 +++++---- src/govuk/components/radios/radios.test.js | 53 ++++++++---- src/govuk/components/tabs/tabs.test.js | 47 ++++++----- 12 files changed, 299 insertions(+), 219 deletions(-) diff --git a/lib/puppeteer-helpers.js b/lib/puppeteer-helpers.js index abf8520ba4..3c00751bae 100644 --- a/lib/puppeteer-helpers.js +++ b/lib/puppeteer-helpers.js @@ -62,13 +62,31 @@ async function renderAndInitialise (page, componentName, options = {}) { * @returns {Promise} Puppeteer page object */ async function goTo (page, path) { - await page.goto(`http://localhost:${PORT}${path}`, { waitUntil: 'load' }) - await page.bringToFront() + const { href } = new URL(path, `http://localhost:${PORT}`) + await page.goto(href) return page } +/** + * Navigate to component preview page + * + * @param {import('puppeteer').Page} page - Puppeteer page object + * @param {string} componentName - Component name + * @param {object} [options] - Component options + * @param {string} options.exampleName - Example name + * @returns {Promise} Puppeteer page object + */ +function goToComponent (page, componentName, { exampleName } = {}) { + const componentPath = exampleName + ? `/components/${componentName}/${exampleName}/preview` + : `/components/${componentName}/preview` + + return goTo(page, componentPath) +} + module.exports = { renderAndInitialise, - goTo + goTo, + goToComponent } diff --git a/src/govuk/all.test.js b/src/govuk/all.test.js index dc9ce9530a..dc3e3ea076 100644 --- a/src/govuk/all.test.js +++ b/src/govuk/all.test.js @@ -4,11 +4,12 @@ const sassdoc = require('sassdoc') +const { renderSass } = require('../../lib/jest-helpers') +const { goTo } = require('../../lib/puppeteer-helpers') + const configPaths = require('../../config/paths.js') const PORT = configPaths.ports.test -const { renderSass } = require('../../lib/jest-helpers') - const baseUrl = 'http://localhost:' + PORT beforeAll(() => { @@ -24,21 +25,21 @@ beforeAll(() => { describe('GOV.UK Frontend', () => { describe('javascript', () => { it('can be accessed via `GOVUKFrontend`', async () => { - await page.goto(baseUrl + '/', { waitUntil: 'load' }) + await goTo(page, '/') const GOVUKFrontendGlobal = await page.evaluate(() => window.GOVUKFrontend) expect(typeof GOVUKFrontendGlobal).toBe('object') }) it('exports `initAll` function', async () => { - await page.goto(baseUrl + '/', { waitUntil: 'load' }) + await goTo(page, '/') const typeofInitAll = await page.evaluate(() => typeof window.GOVUKFrontend.initAll) expect(typeofInitAll).toEqual('function') }) it('exports Components', async () => { - await page.goto(baseUrl + '/', { waitUntil: 'load' }) + await goTo(page, '/') const GOVUKFrontendGlobal = await page.evaluate(() => window.GOVUKFrontend) @@ -60,7 +61,7 @@ describe('GOV.UK Frontend', () => { ]) }) it('exported Components have an init function', async () => { - await page.goto(baseUrl + '/', { waitUntil: 'load' }) + await goTo(page, '/') var componentsWithoutInitFunctions = await page.evaluate(() => { var components = Object.keys(window.GOVUKFrontend) diff --git a/src/govuk/components/accordion/accordion.test.js b/src/govuk/components/accordion/accordion.test.js index 29ff7533de..1a56209905 100644 --- a/src/govuk/components/accordion/accordion.test.js +++ b/src/govuk/components/accordion/accordion.test.js @@ -2,6 +2,8 @@ * @jest-environment puppeteer */ +const { goToComponent } = require('../../../../lib/puppeteer-helpers') + const configPaths = require('../../../../config/paths.js') const PORT = configPaths.ports.test @@ -19,7 +21,7 @@ describe('/components/accordion', () => { }) it('falls back to making all accordion sections visible', async () => { - await page.goto(baseUrl + '/components/accordion/preview', { waitUntil: 'load' }) + await goToComponent(page, 'accordion') const numberOfExampleSections = 2 @@ -32,7 +34,7 @@ describe('/components/accordion', () => { }) it('does not display "↓/↑" in the section headings', async () => { - await page.goto(baseUrl + '/components/accordion/preview', { waitUntil: 'load' }) + await goToComponent(page, 'accordion') const numberOfIcons = await page.evaluate(() => document.body.querySelectorAll('.govuk-accordion .govuk-accordion__section .govuk-accordion-nav__chevron').length) expect(numberOfIcons).toEqual(0) @@ -46,7 +48,7 @@ describe('/components/accordion', () => { }) it('should indicate that the sections are not expanded', async () => { - await page.goto(baseUrl + '/components/accordion/preview', { waitUntil: 'load' }) + await goToComponent(page, 'accordion') const numberOfExampleSections = 2 @@ -60,7 +62,7 @@ describe('/components/accordion', () => { }) it('should change the Show all sections button to Hide all sections when both sections are opened', async () => { - await page.goto(baseUrl + '/components/accordion/preview', { waitUntil: 'load' }) + await goToComponent(page, 'accordion') await page.click('.govuk-accordion .govuk-accordion__section:nth-of-type(2) .govuk-accordion__section-header') await page.click('.govuk-accordion .govuk-accordion__section:nth-of-type(3) .govuk-accordion__section-header') @@ -72,7 +74,7 @@ describe('/components/accordion', () => { }) it('should open both sections when the Show all sections button is clicked', async () => { - await page.goto(baseUrl + '/components/accordion/preview', { waitUntil: 'load' }) + await goToComponent(page, 'accordion') await page.click('.govuk-accordion__show-all') @@ -86,7 +88,9 @@ describe('/components/accordion', () => { }) it('should already have all sections open if they have the expanded class', async () => { - await page.goto(baseUrl + '/components/accordion/with-all-sections-already-open/preview', { waitUntil: 'load' }) + await goToComponent(page, 'accordion', { + exampleName: 'with-all-sections-already-open' + }) const numberOfExampleSections = 2 @@ -106,7 +110,7 @@ describe('/components/accordion', () => { it('should maintain the expanded state after a page refresh', async () => { const sectionHeaderButton = '.govuk-accordion .govuk-accordion__section:nth-of-type(2) .govuk-accordion__section-button' - await page.goto(baseUrl + '/components/accordion/preview', { waitUntil: 'load' }) + await goToComponent(page, 'accordion') await page.click(sectionHeaderButton) const expandedState = await page.evaluate((sectionHeaderButton) => { @@ -125,7 +129,7 @@ describe('/components/accordion', () => { }) it('should transform the button span to