diff --git a/.eslintrc.js b/.eslintrc.js index be05f0756..44070261b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,7 +41,12 @@ module.exports = { }, }, { - files: ["tests/**/*.js", "spatial-data/**/*.js", "web/**/tests/**/*.js"], + files: [ + "tests/**/*.js", + "spatial-data/**/*.js", + "web/**/tests/**/*.js", + "playwright.config.js", + ], extends: ["airbnb-base", "prettier", "plugin:cypress/recommended"], env: { es6: true, diff --git a/.github/actions/build-drupal-image/action.yml b/.github/actions/build-drupal-image/action.yml index 50ab17367..430a18030 100644 --- a/.github/actions/build-drupal-image/action.yml +++ b/.github/actions/build-drupal-image/action.yml @@ -2,19 +2,18 @@ runs: using: composite steps: - name: setup image cacheing - if: needs.should-test.outputs.yes == 'true' uses: actions/cache@v4 - id: cache + id: docker-cache with: key: drupal-image-${{ hashFiles('composer.lock','web/sites/example.settings.dev.php','Dockerfile.dev') }} path: /tmp/image.tar - name: Set up Docker Buildx - if: needs.should-test.outputs.yes == 'true' && steps.cache.outputs.cache-hit != 'true' + if: steps.docker-cache.outputs.cache-hit != 'true' uses: docker/setup-buildx-action@v3 - name: build and export - if: needs.should-test.outputs.yes == 'true' && steps.cache.outputs.cache-hit != 'true' + if: steps.docker-cache.outputs.cache-hit != 'true' uses: docker/build-push-action@v5 with: context: . diff --git a/.github/actions/populate-database/action.yml b/.github/actions/populate-database/action.yml index a1c98289a..4adc7545d 100644 --- a/.github/actions/populate-database/action.yml +++ b/.github/actions/populate-database/action.yml @@ -2,7 +2,6 @@ runs: using: composite steps: - name: database dump cache - if: needs.should-test.outputs.yes == 'true' uses: actions/cache@v4 id: db-cache with: @@ -10,14 +9,14 @@ runs: path: weathergov.sql - name: setup image cacheing - if: needs.should-test.outputs.yes == 'true' && steps.db-cache.outputs.cache-hit != 'true' + if: steps.db-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 with: key: drupal-image-${{ hashFiles('composer.lock','web/sites/example.settings.dev.php','Dockerfile.dev') }} path: /tmp/image.tar - name: cache spatial data - if: needs.should-test.outputs.yes == 'true' && steps.db-cache.outputs.cache-hit != 'true' + if: steps.db-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 with: key: spatial-data-05mr24 @@ -26,7 +25,7 @@ runs: spatial-data/s_05mr24.zip - name: start the site - if: needs.should-test.outputs.yes == 'true' && steps.db-cache.outputs.cache-hit != 'true' + if: steps.db-cache.outputs.cache-hit != 'true' shell: bash run: | docker load --input /tmp/image.tar @@ -34,12 +33,12 @@ runs: # Give the containers a moment to settle. - name: wait a tick - if: needs.should-test.outputs.yes == 'true' && steps.db-cache.outputs.cache-hit != 'true' + if: steps.db-cache.outputs.cache-hit != 'true' shell: bash run: sleep 10 - name: populate the site - if: needs.should-test.outputs.yes == 'true' && steps.db-cache.outputs.cache-hit != 'true' + if: steps.db-cache.outputs.cache-hit != 'true' shell: bash run: | cp web/sites/example.settings.dev.php web/sites/settings.dev.php @@ -47,7 +46,7 @@ runs: make load-spatial - name: dump database - if: needs.should-test.outputs.yes == 'true' && steps.db-cache.outputs.cache-hit != 'true' + if: steps.db-cache.outputs.cache-hit != 'true' shell: bash run: | mysqldump \ diff --git a/.github/actions/setup-site/action.yml b/.github/actions/setup-site/action.yml index 56c771df4..107c2f180 100644 --- a/.github/actions/setup-site/action.yml +++ b/.github/actions/setup-site/action.yml @@ -2,14 +2,12 @@ runs: using: composite steps: - name: setup image cacheing - if: needs.should-test.outputs.yes == 'true' uses: actions/cache@v4 with: key: drupal-image-${{ hashFiles('composer.lock','web/sites/example.settings.dev.php','Dockerfile.dev') }} path: /tmp/image.tar - name: database dump cache - if: needs.should-test.outputs.yes == 'true' uses: actions/cache@v4 id: db-cache with: @@ -17,7 +15,6 @@ runs: path: weathergov.sql - name: start the site - if: needs.should-test.outputs.yes == 'true' shell: bash run: | mkdir .coverage @@ -27,12 +24,10 @@ runs: # Give the containers a moment to settle. - name: wait a tick - if: needs.should-test.outputs.yes == 'true' shell: bash run: sleep 10 - name: populate the site - if: needs.should-test.outputs.yes == 'true' shell: bash run: | cp web/sites/example.settings.dev.php web/sites/settings.dev.php diff --git a/.github/workflows/code-standards.yaml b/.github/workflows/code-standards.yaml index 427ceac7c..13765e552 100644 --- a/.github/workflows/code-standards.yaml +++ b/.github/workflows/code-standards.yaml @@ -265,12 +265,15 @@ jobs: filename: clover.xml metric: statements - end-to-end-tests: - name: end-to-end tests + accessibility-tests: + name: accessibility tests runs-on: ubuntu-latest needs: [populate-database, should-test] steps: + - uses: browser-actions/setup-chrome@v1 + - uses: browser-actions/setup-edge@v1 + - name: checkout if: needs.should-test.outputs.yes == 'true' uses: actions/checkout@v4 @@ -279,22 +282,21 @@ jobs: if: needs.should-test.outputs.yes == 'true' uses: ./.github/actions/setup-site - - name: Cypress run - if: needs.should-test.outputs.yes == 'true' - uses: cypress-io/github-action@v6 + - uses: actions/setup-node@v4 with: - project: tests/e2e - cache-key: cypress-e2e-${{ hashFiles('package-lock.json') }} + node-version: 18 - - name: save screenshots - if: needs.should-test.outputs.yes == 'true' - uses: actions/upload-artifact@v4 - with: - name: screenshots - path: tests/e2e/cypress/screenshots/screenshots.cy.js/ + - name: install dependencies + run: npm ci - accessibility-tests: - name: accessibility tests + - name: install browsers + run: npx playwright install --with-deps + + - name: run tests + run: npx playwright test a11y + + end-to-end-tests: + name: end-to-end tests runs-on: ubuntu-latest needs: [populate-database, should-test] @@ -302,8 +304,6 @@ jobs: - name: checkout if: needs.should-test.outputs.yes == 'true' uses: actions/checkout@v4 - with: - lfs: true - name: setup site if: needs.should-test.outputs.yes == 'true' @@ -313,8 +313,15 @@ jobs: if: needs.should-test.outputs.yes == 'true' uses: cypress-io/github-action@v6 with: - project: tests/a11y - cache-key: cypress-a11y-${{ hashFiles('package-lock.json') }} + project: tests/e2e + cache-key: cypress-e2e-${{ hashFiles('package-lock.json') }} + + - name: save screenshots + if: needs.should-test.outputs.yes == 'true' + uses: actions/upload-artifact@v4 + with: + name: screenshots + path: tests/e2e/cypress/screenshots/screenshots.cy.js/ page-load-time-tests: name: page load time tests diff --git a/.gitignore b/.gitignore index 3bf4b867d..22769f193 100644 --- a/.gitignore +++ b/.gitignore @@ -100,4 +100,8 @@ spatial-data/*.shx spatial-data/*.shp spatial-data/*.txt !spatial-data/us.cities500.txt.zip -/.gitattributes \ No newline at end of file +/.gitattributes +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/Makefile b/Makefile index 60fd3f2de..3bc92796f 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ load-spatial: # Load spatial data into the database ### Testing a11y: accessibility-test accessibility-test: ## Run accessibility tests (alias a11y) - npx cypress run --project tests/a11y + npx playwright test a11y be: backend-test backend-test: ## Run all backend tests. (alias be) diff --git a/docs/dev/index.md b/docs/dev/index.md index 4027d9005..e900329ac 100644 --- a/docs/dev/index.md +++ b/docs/dev/index.md @@ -25,10 +25,11 @@ then: site with our content. 5. Run `npm ci` from the command line. This installs our Javascript and Sass code linters and formatters, as well as - Cypress, which is necessary for end-to-end and accessibility testing. -6. Browse to [http://localhost:8080](http://localhost:8080) in your broswer. You + Playwright, which is necessary for end-to-end and accessibility testing. +6. Run `npx playwright install --with-deps` to install Playwright's browsers. +7. Browse to [http://localhost:8080](http://localhost:8080) in your broswer. You should see our front page! Congrats! -7. Browse to [http://localhost:8080/user/login](http://localhost:8080/user/login) +8. Browse to [http://localhost:8080/user/login](http://localhost:8080/user/login) to log in. Your username is `admin` and your password is `root`. Then you can do stuff! diff --git a/docs/dev/testing.md b/docs/dev/testing.md index 34c8eb1a6..fb4913d35 100644 --- a/docs/dev/testing.md +++ b/docs/dev/testing.md @@ -102,12 +102,12 @@ make ee Automated accessibility testing can identify about half of all kinds of accessibility bugs, so it is not a total solution. However, even a partial -automated solution is helpful! We also use [Cypress](https://www.cypress.io) for -accessibility testing, with the addition of the -[cypress-axe](https://www.npmjs.com/package/cypress-axe) library. This library -integrates [Axe core](https://github.com/dequelabs/axe-core) to test the -rendered page. We use the [WCAG2AA](https://www.w3.org/WAI/WCAG2AA-Conformance) -standard. +automated solution is helpful! We also use [Playwright](https://playwright.dev/) +for accessibility testing, with the addition of the +[@axe-core/playwright](https://www.npmjs.com/package/@axe-core/playwright) +library. This library integrates [Axe core](https://github.com/dequelabs/axe-core) +to test the rendered page. We use the +[WCAG2AA](https://www.w3.org/WAI/WCAG2AA-Conformance) standard. To run accessibility tests locally, the Makefile command is: diff --git a/package-lock.json b/package-lock.json index 4a055a62a..0687ceff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,10 @@ "shapefile": "^0.6.6" }, "devDependencies": { - "axe-core": "^4.8.4", + "@axe-core/playwright": "^4.9.0", + "@playwright/test": "^1.43.1", + "@types/node": "^20.12.8", + "axe-core": "^4.9.1", "chai": "^5.1.0", "cypress": "^13.8.1", "cypress-axe": "^1.5.0", @@ -29,6 +32,7 @@ "jsdom-global": "^3.0.2", "mocha": "^10.4.0", "mocha-cli": "^1.0.1", + "playwright": "^1.43.1", "prettier": "^3.2.5", "sinon": "^17.0.1", "stylelint": "^16.4.0", @@ -46,6 +50,18 @@ "node": ">=0.10.0" } }, + "node_modules/@axe-core/playwright": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.9.0.tgz", + "integrity": "sha512-Q1Lz75dNsX38jof+aev7RficDMdH/HLOLySkDdXR0fUoeFcLdw4UNgDO2CNNP4CTpoesEdfYRdd6VmDXjhBgbA==", + "dev": true, + "dependencies": { + "axe-core": "~4.9.0" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", @@ -445,6 +461,21 @@ "node": ">= 8" } }, + "node_modules/@playwright/test": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", + "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", + "dev": true, + "dependencies": { + "playwright": "1.43.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@prettier/plugin-php": { "version": "0.22.2", "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.22.2.tgz", @@ -513,9 +544,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.6.tgz", - "integrity": "sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ==", + "version": "20.12.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz", + "integrity": "sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==", "dependencies": { "undici-types": "~5.26.4" } @@ -1000,9 +1031,9 @@ "dev": true }, "node_modules/axe-core": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.0.tgz", - "integrity": "sha512-H5orY+M2Fr56DWmMFpMrq5Ge93qjNdPVqzBv5gWK3aD1OvjBEJlEzxf09z93dGVQeI0LiW+aCMIx1QtShC/zUw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", + "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", "dev": true, "engines": { "node": ">=4" @@ -5137,6 +5168,50 @@ "integrity": "sha512-VJK1SRmXBpjwsB4YOHYSturx48rLKMzHgCqDH2ZDa6ZbMS/N5huoNqyQdK5Fj/xayu3fqbXckn5SeCS1EbMDZg==", "dev": true }, + "node_modules/playwright": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz", + "integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==", + "dev": true, + "dependencies": { + "playwright-core": "1.43.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz", + "integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", diff --git a/package.json b/package.json index e4b406f70..86a189f10 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,10 @@ "doc": "docs" }, "devDependencies": { - "axe-core": "^4.8.4", + "@axe-core/playwright": "^4.9.0", + "@playwright/test": "^1.43.1", + "@types/node": "^20.12.8", + "axe-core": "^4.9.1", "chai": "^5.1.0", "cypress": "^13.8.1", "cypress-axe": "^1.5.0", @@ -39,6 +42,7 @@ "jsdom-global": "^3.0.2", "mocha": "^10.4.0", "mocha-cli": "^1.0.1", + "playwright": "^1.43.1", "prettier": "^3.2.5", "sinon": "^17.0.1", "stylelint": "^16.4.0", @@ -48,7 +52,6 @@ }, "scripts": { "load-spatial": "cd spatial-data && node load-shapefiles.js", - "cypress:a11y": "cypress open --project tests/a11y", "cypress:e2e": "cypress open --project tests/e2e", "js-component-tests": "npx mocha web/themes/**/tests/components/*-tests.js", "js-format": "npx prettier -w 'web/themes/**/assets/**/*.js' 'tests/**/*.js' '*.js'", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 000000000..00191efaf --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,71 @@ +// @ts-check +const { defineConfig, devices } = require("@playwright/test"); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = defineConfig({ + testDir: "./tests/playwright", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: process.env.CI ? "github" : "line", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:8080", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + { + name: "Mobile Chrome", + use: { ...devices["Pixel 5"] }, + }, + { + name: "Mobile Safari", + use: { ...devices["iPhone 12"] }, + }, + + /* Test against branded browsers. */ + { + name: "Microsoft Edge", + use: { ...devices["Desktop Edge"], channel: "msedge" }, + }, + { + name: "Google Chrome", + use: { ...devices["Desktop Chrome"], channel: "chrome" }, + }, + ], +}); diff --git a/tests/a11y/cypress.config.js b/tests/a11y/cypress.config.js deleted file mode 100644 index 804c1bc25..000000000 --- a/tests/a11y/cypress.config.js +++ /dev/null @@ -1,27 +0,0 @@ -const { defineConfig } = require("cypress"); - -module.exports = defineConfig({ - e2e: { - baseUrl: "http://localhost:8080", - screenshotOnRunFailure: false, - setupNodeEvents: (on) => { - on("task", { - a11yViolation: ({ node, url, description }) => { - const instances = [...node.any, ...node.all]; - if (instances.length) { - for (const violation of instances) { - console.log( - `::error Accessibility error.::${violation.message} at ${node.html} (${description}: ${url}) [selector: ${node.target[0]}]`, - ); - } - } else { - console.log( - `::error Accessibility error.::${node.failureSummary.replace(/\n/g, " *")} at ${node.html} (${description}): ${url}) [selector: ${node.target[0]}]`, - ); - } - return null; - }, - }); - }, - }, -}); diff --git a/tests/a11y/cypress/e2e/axe-accessibility-tests.cy.js b/tests/a11y/cypress/e2e/axe-accessibility-tests.cy.js deleted file mode 100644 index d9a89c598..000000000 --- a/tests/a11y/cypress/e2e/axe-accessibility-tests.cy.js +++ /dev/null @@ -1,43 +0,0 @@ -// eslint-disable-next-line import/extensions -const pages = require("../../../pages.json"); - -const viewports = [ - ["phone", 480, 500], - ["tablet", 640, 500], - ["desktop", 1024, 500], -]; - -describe("accessibility tests", () => { - before(() => { - cy.request("http://localhost:8081/play/testing"); - }); - - pages.forEach(({ name, url }) => { - describe(name, () => { - viewports.forEach(([viewport, width, height]) => { - it(`${viewport} (width: ${width})`, () => { - cy.visit(url); - - cy.get("html").invoke("css", "height", "initial"); - cy.get("body").invoke("css", "height", "initial"); - cy.viewport(width, height); - - cy.injectAxe(); - cy.configureAxe({ runOnly: { values: ["wcag2aa"] } }); - cy.checkA11y(null, null, (violations) => { - for (const violation of violations) { - for (const node of violation.nodes) { - cy.task("a11yViolation", { - violation, - node, - url, - description: `${name} @ ${viewport}`, - }); - } - } - }); - }); - }); - }); - }); -}); diff --git a/tests/a11y/cypress/e2e/dates-are-localized.cy.js b/tests/a11y/cypress/e2e/dates-are-localized.cy.js deleted file mode 100644 index f71ccf891..000000000 --- a/tests/a11y/cypress/e2e/dates-are-localized.cy.js +++ /dev/null @@ -1,40 +0,0 @@ -const dayjs = require("dayjs"); - -describe("main script", () => { - before(() => { - cy.request("http://localhost:8081/play/testing"); - }); - - const obsTime = dayjs("2024-02-20T15:53:00+00:00").toDate(); - - describe("formats the narrative timestamp according to the browser's locale settings", () => { - // US English, Mexican Spanish, Puerto Rican Spanish, and US Spanish. Just - // some representative test cases. If these pass, others should too. - ["en-US", "es-MX", "es-PR", "es-US"].forEach((locale) => { - it(`for the ${locale} locale`, () => { - const formatter = new Intl.DateTimeFormat(locale, { - weekday: "long", - hour: "numeric", - minute: "2-digit", - timeZoneName: "short", - }); - - cy.visit("/point/33.521/-86.812/#current", { - onBeforeLoad: (win) => { - Object.defineProperty(win.navigator, "languages", { - value: [locale], - }); - }, - }); - - cy.get(".usa-sr-only").then((sr) => { - expect(sr.length).to.be.greaterThan(0); - - const expected = formatter.format(obsTime); - console.log(expected); - expect(sr).to.contain(expected); - }); - }); - }); - }); -}); diff --git a/tests/a11y/cypress/fixtures/example.json b/tests/a11y/cypress/fixtures/example.json deleted file mode 100644 index 02e425437..000000000 --- a/tests/a11y/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/tests/a11y/cypress/support/commands.js b/tests/a11y/cypress/support/commands.js deleted file mode 100644 index 119ab03f7..000000000 --- a/tests/a11y/cypress/support/commands.js +++ /dev/null @@ -1,25 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/tests/a11y/cypress/support/e2e.js b/tests/a11y/cypress/support/e2e.js deleted file mode 100644 index 5c5c4656d..000000000 --- a/tests/a11y/cypress/support/e2e.js +++ /dev/null @@ -1,21 +0,0 @@ -// *********************************************************** -// This example support/e2e.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import "./index"; -import "./commands"; - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/tests/a11y/cypress/support/index.js b/tests/a11y/cypress/support/index.js deleted file mode 100644 index 743ad59d6..000000000 --- a/tests/a11y/cypress/support/index.js +++ /dev/null @@ -1 +0,0 @@ -require("cypress-axe"); diff --git a/tests/playwright/a11y.spec.js b/tests/playwright/a11y.spec.js new file mode 100644 index 000000000..7b115a8bd --- /dev/null +++ b/tests/playwright/a11y.spec.js @@ -0,0 +1,57 @@ +const { test, expect } = require("@playwright/test"); +const AxeBuilder = require("@axe-core/playwright"); + +const pages = [ + { name: "front page", url: "/" }, + { name: "login page", url: "/user/login" }, + { + name: "location page with alerts (alerts tab)", + url: "/point/33.521/-86.812#alerts", + }, + { + name: "location page with alerts (today tab)", + url: "/point/33.521/-86.812#today", + }, + { + name: "location page with alerts (daily tab)", + url: "/point/33.521/-86.812#daily", + }, + { + name: "location page without alerts (today tab)", + url: "/point/35.198/-111.651#today", + }, + { + name: "location page without alerts (daily tab)", + url: "/point/35.198/-111.651#daily", + }, +]; + +for (const { name, url } of pages) { + test.describe(name, () => { + test("should not have any automatically-detectable accessibility issues", async ({ + page, + }) => { + await page.goto(url); + + const accessibilityScanResults = await new AxeBuilder({ page }) + .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]) + .analyze(); + + accessibilityScanResults.incomplete.forEach( + ({ id, description, nodes }) => { + if (process.env.CI) + console.log( + `::warning title=${name} accessibility::(${id}) - ${description} [selectors: ${nodes.map(({ target }) => target).join(", ")}]`, + ); + else { + console.log(`${name} accessibility + ${id} - ${description} + - ${nodes.map(({ target }) => target).join("\n - ")}`); + } + }, + ); + + expect(accessibilityScanResults.violations).toEqual([]); + }); + }); +} diff --git a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test index b2ed81202..ca3a0d792 100644 --- a/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test +++ b/web/modules/weather_data/src/Service/Test/WeatherAlertParser.php.test @@ -102,6 +102,8 @@ final class WeatherAlertParserTest extends TestCase $rawDescription = "...WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK... +This bit...has ellipses in the middle...but is not an overview. + .After a few days of warm weather, a potent winter storm will bring windy and colder conditions with periods of heavy snow to the Sierra and higher elevations of northeast California later this week. While @@ -137,6 +139,11 @@ Wednesday, then drop to near 5500 feet Wednesday night and near "text" => "WINTER CONDITIONS RETURN TO THE SIERRA AND NORTHEAST CALIFORNIA FOR MID-LATE WEEK", ], + [ + "type" => "paragraph", + "text" => + "This bit...has ellipses in the middle...but is not an overview.", + ], [ "type" => "paragraph", "text" => diff --git a/web/modules/weather_data/src/Service/WeatherAlertParser.php b/web/modules/weather_data/src/Service/WeatherAlertParser.php index 60e9bf080..11b131b95 100644 --- a/web/modules/weather_data/src/Service/WeatherAlertParser.php +++ b/web/modules/weather_data/src/Service/WeatherAlertParser.php @@ -80,7 +80,7 @@ public function parse() */ public function parseOverview($str) { - $regex = "/\.\.\.([^\.]+)\.\.\./"; + $regex = "/^\.\.\.([^\.]+)\.\.\.$/"; if (preg_match($regex, $str, $matches)) { array_push($this->parsedNodes, [ "type" => "paragraph", diff --git a/web/themes/new_weather_theme/templates/field/field--node--field-facebook-url--wfo-promo.html.twig b/web/themes/new_weather_theme/templates/field/field--node--field-facebook-url--wfo-promo.html.twig index 332a0fbc5..48eded5dd 100644 --- a/web/themes/new_weather_theme/templates/field/field--node--field-facebook-url--wfo-promo.html.twig +++ b/web/themes/new_weather_theme/templates/field/field--node--field-facebook-url--wfo-promo.html.twig @@ -1,6 +1,6 @@ - diff --git a/web/themes/new_weather_theme/templates/field/field--node--field-twitter-url--wfo-promo.html.twig b/web/themes/new_weather_theme/templates/field/field--node--field-twitter-url--wfo-promo.html.twig index d0c2d8726..7debbc0f6 100644 --- a/web/themes/new_weather_theme/templates/field/field--node--field-twitter-url--wfo-promo.html.twig +++ b/web/themes/new_weather_theme/templates/field/field--node--field-twitter-url--wfo-promo.html.twig @@ -1,6 +1,6 @@ - diff --git a/web/themes/new_weather_theme/templates/field/field--node--field-youtube-url--wfo-promo.html.twig b/web/themes/new_weather_theme/templates/field/field--node--field-youtube-url--wfo-promo.html.twig index c47590b68..7239ea46e 100644 --- a/web/themes/new_weather_theme/templates/field/field--node--field-youtube-url--wfo-promo.html.twig +++ b/web/themes/new_weather_theme/templates/field/field--node--field-youtube-url--wfo-promo.html.twig @@ -1,6 +1,6 @@ -