<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Craig Morten on Medium]]></title>
        <description><![CDATA[Stories by Craig Morten on Medium]]></description>
        <link>https://medium.com/@craigmorten?source=rss-c6b59d857cb7------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*A81L1TJFIQZWUMwisqSdGw.png</url>
            <title>Stories by Craig Morten on Medium</title>
            <link>https://medium.com/@craigmorten?source=rss-c6b59d857cb7------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 16 May 2026 18:07:10 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@craigmorten/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Automating A11y Testing: Part 3 — Testing Library and Beyond]]></title>
            <link>https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-3-testing-library-and-beyond-f4cd590c7395?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/f4cd590c7395</guid>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Wed, 14 Feb 2024 13:58:26 GMT</pubDate>
            <atom:updated>2024-02-16T17:53:24.533Z</atom:updated>
            <content:encoded><![CDATA[<h3>Automating A11y Testing: Part 3 — Testing Library and Beyond</h3><p>Last year I gave a talk on the topic of automating web accessibility (a11y) testing at the John Lewis Partnership Tech Profession Conference 2023 which I have converted into this “Automating a11y testing” article series.</p><p><a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126">Part 1</a> of the series covered a number of tools from the <a href="https://www.deque.com/axe/">Axe suite</a> for static analysis of sites, and <a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-2-beyond-axe-af0f32f366e7">Part 2</a> started to explore some of the non-Axe tooling in the wider a11y test automation space.</p><p>In this Part 3 we will start to explore how we can weave accessibility concerns into our day to day unit and integration test automation.</p><p>Now remember from <a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126">Part 1</a>…</p><blockquote>It is also key to remember the importance of manual validation and exploratory testing.</blockquote><blockquote>At the end of the day it is <em>real people</em> that are visiting and using your site, so it is incredibly important to ensure the quality of their experience, not just to tickbox that automation has passed.</blockquote><p>Keeping this in mind, let’s take a dive into something a lot of folks in the front-end world have probably encountered — <a href="https://testing-library.com/">Testing Library</a>!</p><h3>Introducing Testing Library</h3><p>Testing Library is a collection of packages for supplementing test frameworks that allow you to query and assert on your application similar to how your users would use it.</p><blockquote>“The more your tests resemble the way your software is used, the more confidence they can give you.”</blockquote><p>Instead of testing against abstract components that you write for your framework of choice (like <a href="https://github.com/enzymejs/enzyme">Enzyme</a> and other libraries might do), Testing Library ensures you are testing against the final rendered result of DOM nodes to bring your tests closer to reality.</p><p><em>So what does this have to do with accessibility automation?</em></p><figure><img alt="Laptop on a desk with code open in an editor." src="https://cdn-images-1.medium.com/max/1024/0*0hAHDfy7t9r6QlEQ" /><figcaption>Photo by <a href="https://unsplash.com/@jstrippa?utm_source=medium&amp;utm_medium=referral">James Harrison</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The key is in the three groups of utility queries that Testing Library provides:</p><p><strong>Queries accessible to everyone</strong></p><p>These queries not only reflect the experience of visual users but also those who use assistive technologies to visit your pages:</p><ol><li>ByRole</li><li>ByLabelText</li><li>ByPlaceholderText</li><li>ByText</li><li>ByDisplayValue</li></ol><p>Where you mess up, these queries make it immediately clear that something is amiss, whereas other queries or frameworks may miss mistakes (more on this later!).</p><p>Not able to find that button or a crucial piece of copy? With these queries you can be confident when something isn’t right with your code, and that it will be caught with a helpful error message explaining why it couldn’t find what you were looking for and suggestions as to alternative options.</p><p>Consider this example React Testing Library test where we have accidentally used the incorrect semantic element for a button:</p><pre>import React from &#39;react&#39;;<br>import { render, screen } from &#39;@testing-library/react&#39;;<br><br>describe(&#39;Submit button&#39;, () =&gt; {<br>  test(&#39;should render a &quot;Submit&quot; button&#39;, () =&gt; {<br>    render(&lt;div className=&quot;button&quot;&gt;Submit&lt;/div&gt;);<br><br>    expect(screen.getByRole(&#39;button&#39;, { name: &#39;Submit&#39; })).toBeInTheDocument();<br>  });<br>});</pre><p>This will result in the following output making it clear that something is inaccessible about our button, in this case because we’ve used a &lt;div&gt; instead of a &lt;button&gt;:</p><pre>TestingLibraryElementError: Unable to find an accessible element with the role &quot;button&quot; and name &quot;Submit&quot;<br><br>There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole<br><br>Ignored nodes: comments, &lt;script /&gt;, &lt;style /&gt;<br>&lt;body&gt;<br>  &lt;div&gt;<br>    &lt;div<br>       class=&quot;button&quot;<br>    &gt;<br>      Submit<br>    &lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/body&gt;</pre><p>How about this example where we have accidentally hidden the button?</p><pre>import React from &#39;react&#39;;<br>import { render, screen } from &#39;@testing-library/react&#39;;<br><br>describe(&#39;Submit button&#39;, () =&gt; {<br>  test(&#39;should render a &quot;Submit&quot; button&#39;, () =&gt; {<br>    render(<br>      &lt;button type=&quot;button&quot; style={{ display: &#39;none&#39; }}&gt;<br>        Submit<br>      &lt;/button&gt;,<br>    );<br><br>    expect(screen.getByRole(&#39;button&#39;, { name: &#39;Submit&#39; })).toBeInTheDocument();<br>  });<br>});</pre><p>Again, unlike other libraries that would probably miss this, Testing Library gives you helpful output to make it clear that the button is still not accessible:</p><pre>TestingLibraryElementError: Unable to find an accessible element with the role &quot;button&quot; and name &quot;Submit&quot;<br><br>There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole<br><br>Ignored nodes: comments, &lt;script /&gt;, &lt;style /&gt;<br>&lt;body&gt;<br>  &lt;div&gt;<br>    &lt;button<br>      style=&quot;display: none;&quot;<br>      type=&quot;button&quot;<br>    &gt;<br>      Submit<br>    &lt;/button&gt;<br>  &lt;/div&gt;<br>&lt;/body&gt;</pre><p>By placing the emphasis on querying elements using their accessible role and labels you will naturally start to write components for which you can be confident of the correct element choice and that content will be announced as you expect for all users, no matter how they use your application.</p><p><strong>Semantic queries</strong></p><p>These queries allow you assert on elements that use HTML5 and ARIA compliant attributes to provide additional context to users:</p><ol><li>ByAltText</li><li>ByTitle</li></ol><p>The use-cases for these are limited — often you can use one of the previous queries to acheive the same thing, for example passing the alt as the accessible name option to the ByRole query. Where your use-case demands it these are still preferable to the next class of query, keeping a focus on the end user and the content they will be presented with.</p><p><strong>Test ID queries</strong></p><p>Lastly, there is the option to use ByTestId to query elements with data-testid attributes.</p><p>These attributes cannot be seen or heard by users in any way so should only be used as a last resort for test cases where matching on the role or text for an element doesn’t make sense.</p><p>If you use these queries for testing components that have important value to your users it can be <strong>very </strong>easy to miss that you’re not using the right semantic element or the text isn’t accessible, giving you false confidence in your code.</p><p>For example, let’s consider the submit button again, but using ByTestId instead of ByRole:</p><pre>import React from &#39;react&#39;;<br>import { render, screen } from &#39;@testing-library/react&#39;;<br><br>describe(&#39;Submit button&#39;, () =&gt; {<br>  test(&#39;should render a &quot;Submit&quot; button&#39;, () =&gt; {<br>    render(<br>      &lt;div data-testid=&quot;button&quot; style={{ display: &#39;none&#39; }}&gt;<br>        Soobmit<br>      &lt;/div&gt;,<br>    );<br><br>    expect(screen.getByTestId(&#39;button&#39;)).toBeInTheDocument();<br>  });<br>});</pre><p>There are a plethera of mistakes here from incorrect semantics, typos in the copy, and the fact the button isn’t even displayed to any users… but yet this test will still pass!</p><p>For this reason you should<strong> always </strong>prefer the previous accessible or sematic queries.</p><p>You can learn more about these queries in the <a href="https://testing-library.com/docs/queries/about">Testing Library docs</a>.</p><h4>Weaving accessibility into your unit tests</h4><p>Let’s look at some examples of how we can best use these queries to blend accessibility ideas into our unit tests using the <a href="https://www.npmjs.com/package/@testing-library/dom">@testing-library/dom package</a>.</p><figure><img alt="~ listed on a website with a No.1 Morello Cherry &amp; Almond Tart added to the trolley." src="https://cdn-images-1.medium.com/max/1024/1*mUlI7UhWEl3d5lZTDXPipA.jpeg" /></figure><p><strong>Example 1: Add to trolley button</strong></p><p>Let’s say we had the following button for adding an item to the user’s trolley:</p><pre>&lt;button type=&quot;button&quot; id=&quot;add-to-trolley&quot;&gt;Add To Trolley&lt;/button&gt;</pre><p>If we were writing a test to assert that it performs the expected action we might do something like:</p><pre>const button = document.querySelector(&#39;#add-to-trolley&#39;);<br><br>// ... test code to trigger a click on the button and assert<br>// on the item now being in the trolley.</pre><p>This will certainly get ahold of the button and allow us to write a test, but it falls down in a number of places:</p><ol><li>The id of the button is likely an implementation detail, and certainly not something that users care about. If it were to change the test would fail so we’ve introduced a fragile test!</li><li>If we were to swap the &lt;button&gt; for an &lt;a&gt; the test would still pass. Although the behaviour might still kinda work for visual mouse users, you will baffle your screen reader users who will hear that this is a link, and your Safari keyboard users who won’t be able to Tab to the element.</li><li>This test fails to take the main user facing information into account — the button text for “Add To Trolley”. It wouldn’t be a happy place if the behaviour you were testing was accidentally on the wrong button because there was a mistake with ids, or if it worked but you had a typo on the button for “Add To Troll” that was missed!</li><li>This test would also pass even if the button was hidden using CSS styles 🫣.</li></ol><p>With Testing Library we can cover off all these points with a single line swap out:</p><pre>const button = screen.getByRole(&#39;button&#39;, { name: &#39;Add To Trolley&#39; });<br><br>// ... test code to trigger a click on the button and assert<br>// on the item now being in the trolley.</pre><p>Now not only can we assert on the desired click behaviour but we can also be certain that:</p><p><em>The element is a </em><strong><em>visible</em></strong><em> </em><strong><em>button</em></strong><em> with an </em><strong><em>accessible name of “Add To Trolley”.</em></strong></p><p><strong>Example 2: Lazy loaded image</strong></p><p>Let’s say we had the following lazy loaded image used on a blog post article tile:</p><pre>&lt;img<br>  data-testid=&quot;article-1&quot;<br>  alt=&quot;Young West Highland terrier sitting on the floor by a window&quot;<br>  loading=&quot;lazy&quot;<br>  src=&quot;https://unsplash.com/photos/white-long-coated-dog-sitting-on-floor-f5KQq4Wfxg8&quot; /&gt;</pre><p>If we were writing a test to assert that it had expected behaviours such lazy loading, error handling, impression tracking, etc. we might do something like:</p><pre>const img = document.querySelector(&#39;[data-testid=&quot;article-1&quot;]&#39;);<br><br>// ... test code to assert on attributes and behaviours</pre><p>Again we have successfully retrieved the image element, but we could do better. What if the image is no longer for the first article? What if someone accidentally removed the alternative text in a refactor?</p><p>Again we can use Testing Library to decouple us from implementation detail while gaining confidence that the image being asserted on is the real deal:</p><pre>const img = screen.getByAltText(&#39;Young West Highland terrier sitting on the floor by a window&#39;);<br><br>// ... test code to assert on attributes and behaviours</pre><blockquote>Note: In this example we’ve opted for ByAltText for demonstrative purposes.</blockquote><blockquote>You could also use ByRole with an <a href="https://www.w3.org/TR/wai-aria-1.2/#img">img role</a> and accessible name to disambiguate between it being an image vs an input, area, or any other elements that can have alternative text.</blockquote><h4>Not just for unit tests</h4><p>So is all this goodness reserved just for our unit tests?</p><p><em>Luckily not!</em></p><p>Testing Library boasts a huge number of packages that cover a whole host of different test frameworks from working just with the DOM (as we’ve demonstrated above) to integrating with integration and end-to-end test frameworks such as Cypress:</p><ul><li><a href="https://testing-library.com/docs/dom-testing-library/intro">DOM Testing Library</a></li><li><a href="https://testing-library.com/docs/react-testing-library/intro">React Testing Library</a></li><li><a href="https://testing-library.com/docs/vue-testing-library/intro">Vue Testing Library</a></li><li><a href="https://testing-library.com/docs/angular-testing-library/intro">Angular Testing Library</a></li><li><a href="https://testing-library.com/docs/svelte-testing-library/intro">Svelte Testing Library</a></li><li><a href="https://testing-library.com/docs/marko-testing-library/intro">Marko Testing Library</a></li><li><a href="https://testing-library.com/docs/preact-testing-library/intro">Preact Testing Library</a></li><li><a href="https://testing-library.com/docs/bs-react-testing-library/intro">Reason Testing Library</a></li><li><a href="https://testing-library.com/docs/react-native-testing-library/intro">Native Testing Library</a> (think React Native)</li><li><a href="https://testing-library.com/docs/cypress-testing-library/intro">Cypress Testing Library</a></li><li><a href="https://testing-library.com/docs/pptr-testing-library/intro">Puppeteer Testing Library</a></li><li><a href="https://testing-library.com/docs/testcafe-testing-library/intro">Testcafe Testing Library</a></li><li><a href="https://testing-library.com/docs/nightwatch-testing-library/intro">Nightwatch Testing Library</a></li><li><a href="https://testing-library.com/docs/webdriverio-testing-library/intro">WebdriverIO Testing Library</a></li></ul><p>The eagle-eyed among you may notice that <a href="https://playwright.dev">Playwright</a> is missing from the above list.</p><p>Don’t panic — in both the <a href="https://playwright.dev/docs/api/class-locator#locator-get-by-role">Playwright Locators</a> and the <a href="https://playwright.dev/docs/testing-library">new experimental Playwright Component Testing</a> the Playwright APIs include an equivalent set of queries that mirror the Testing Library queries, making it very easy to write in the same style, or even migrate unit test code to end-to-end test code.</p><h4>Not just queries</h4><p>Thus far we’ve been focusing a lot on the Testing Library queries and how we can use them to ensure the accessibility of elements in our test automation, but the Testing Library ecosystem also boasts a host of other packages and extensions that can also be used in accessibility testing.</p><p>A special mention goes to the <a href="https://github.com/testing-library/user-event">@testing-library/user-event package</a> which attempts to provide utilities that simulate the real events that would happen in the browser when users interact.</p><p>For example, consider the following which uses the package to type a message into a text input:</p><pre>const user = userEvent.setup();<br><br>await user.type(textInput, &#39;A message&#39;);<br><br>// ... remainder of test code</pre><p>Whereas a lot of testing libraries (or something you wrote yourself) would likely dispatch a single DOM input or change event to the text input, the <a href="https://www.npmjs.com/package/@testing-library/user-event">@testing-library/user-event package</a> goes further and tries to simulate the entire interaction:</p><ol><li>A hover event is dispatched to the textInput to simulate moving the cursor over the element.</li><li>A click event is dispatched to the textInput to simulate clicking the element, just as a real user would before they start typing.</li><li>Characters are then dispatched to the textInput one by one, just as a real user would type, with associated events.</li></ol><p>This is far more realistic, and more likely to give you a clear idea how your elements will behave for real mouse, keyboard, and assistive technology users.</p><blockquote>Where assumptions are incorrect (e.g. you don’t “hover” with a keyboard or screen reader) the methods are all highly configurable, with a host of lower level APIs for constructing your own interaction flows.</blockquote><p>With other useful “convenience APIs” such as <a href="https://testing-library.com/docs/user-event/convenience#tab">user.tab()</a> you can also easily cover off keyboard scenarios for your components the same as you would for mouse based test journeys.</p><p>You can learn more about other packages in the <a href="https://testing-library.com/docs/ecosystem-jest-dom">Testing Library ecosystem docs</a>.</p><h3>Testing Library gotchas</h3><p>As with any tool, there are some potential gotchas with Testing Library to be aware of.</p><h4>Too much choice</h4><p>As seen in the second example above, there are often a few ways in which you can use different queries to select the same element.</p><p><em>And with choice comes footguns.</em></p><figure><img alt="Man staring at laptop deep in thought." src="https://cdn-images-1.medium.com/max/1024/0*F1rU7EIGOIGCiahI" /><figcaption>Photo by <a href="https://unsplash.com/@sickhews?utm_source=medium&amp;utm_medium=referral">Wes Hicks</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When you are writing your queries take care to be as explicit as possible for your given scenario, while of course being pragmatic. For example, in the first example it might have been tempting to just select on the button role:</p><pre>expect(screen.getByRole(&#39;button&#39;)).toBeInTheDocument();</pre><p>Although we can be confident that we will still be selecting a button element, unfortunately we now have no confidence that the button has the correct accessible name (if any at all) for customers!</p><p>Similarly you can be caught out with the likes of ByText:</p><pre>expect(screen.getByText(&#39;Add To Trolley&#39;)).toBeInTheDocument();</pre><p>With this test assertion we can certainly be confident the expected text is on the page, but there is <em>no guarantee</em> that this is actually the button element so there is no protection against future refactors to the code which could see the &lt;button&gt; element replaced by something incorrect such as an &lt;a&gt; element.</p><p>As a rule of thumb I would recommend:</p><ol><li>Should the element have <em>any </em>semantic meaning that is important to users? Use ByRole with <strong>both the role and accessible name</strong>.</li><li>If not, should the element have text that is conveyed to users? Use ByText or a similar text query with the accessible name.</li><li>Otherwise… you should probably challenge why you have the element!</li></ol><p>If an element has no semantic meaning and no text content nor accessible label then it’s likely that either:</p><ul><li>You should be using a different element.</li><li>You need to give your element text content or an accessible label such as alt , aria-labelledby, or aria-label.</li><li>You shouldn’t have the element at all!</li></ul><h4>Testing Library is not a real browser</h4><p>Although the library does a great job of helping to write accessible components with a user-first viewpoint of writing tests, it does have a shortcoming in that it isn’t an actual browser.</p><p>Assistive technologies such as screen readers use a combination of the DOM and accessibility APIs in order to understand an element and convey information to users.</p><blockquote>You can learn more about how browsers map HTML elements and attributes to platform accessibility APIs in the <a href="https://www.w3.org/TR/html-aam-1.0/">W3C HTML-AAM specification</a>.</blockquote><p>But much like browsers have varying <a href="https://wpt.fyi/results/?label=master&amp;label=experimental&amp;aligned&amp;q=label%3Aaccessibility">accessibility compliance and support</a>, Testing Library doesn’t always reflect the role or accessible name of an element accurately. In fact, because Testing Library only has access to the DOM and can’t use these accessibility APIs it has to rely heavily upon some awesome packages such as <a href="https://github.com/A11yance/aria-query">aria-query</a> and <a href="https://github.com/eps1lon/dom-accessibility-api">dom-accessibility-api</a> to reflect the <a href="https://www.w3.org/TR/wai-aria-1.2/">WAI-ARIA</a> and <a href="https://w3c.github.io/accname/">ACCNAME</a> W3C specifications, meaning it can easily fall out of sync with the latest standards.</p><p>For example, at the time of writing (Feb 2024) the following roles are missing or incorrect in the Testing Library latest stable versions:</p><ul><li><a href="https://www.w3.org/TR/wai-aria-1.2/#code">code role</a> for the &lt;code&gt; element</li><li><a href="https://www.w3.org/TR/wai-aria-1.3/#mark">mark role</a> for the &lt;mark&gt; element</li><li><a href="https://www.w3.org/TR/wai-aria-1.2/#meter">meter role</a> for the &lt;meter&gt; element</li><li><a href="https://www.w3.org/TR/wai-aria-1.2/#strong">strong role</a> for the &lt;strong&gt; element</li><li><a href="https://www.w3.org/TR/wai-aria-1.2/#paragraph">paragraph role</a> for the &lt;p&gt; element</li><li><a href="https://www.w3.org/TR/wai-aria-1.2/#presentation">presentation role</a> for an <a href="https://www.w3.org/TR/html-aria/#docconformance">&lt;img&gt; element with an empty </a><a href="https://www.w3.org/TR/html-aria/#docconformance">alt=&quot;&quot; attribute</a></li><li>Multiple new element additions for the generic role</li><li>…and much more!</li></ul><p>Luckily for these roles we should see <a href="https://github.com/testing-library/dom-testing-library/pull/1269">support coming soon in v10 of </a><a href="https://github.com/testing-library/dom-testing-library/pull/1269">@testing-library/dom</a> as <a href="https://github.com/testing-library/dom-testing-library/pull/1241">I added the missing roles in the latter part of 2023</a>!</p><p>As always with open source packages — if you spot something that’s not right raise an issue with helpful comments and steps to reproduce (an perhaps even contribute a pull request with the changes if confident!).</p><p><a href="https://discord.com/invite/testing-library">Testing Library also has an active Discord server</a> for asking queries.</p><h4><strong>Lack of “holistic” user experience insight</strong></h4><p>Although Testing Library allows you to weave accessibility concerns and testing into your querying of specific elements, as well as covers off some interaction scenarios, somewhere I feel it falls down is “bigger picture” validation of component or whole site accessibility.</p><p>Consider the following React example for a product tile on an ecommerce site:</p><pre>&lt;ProductTile&gt;<br>  &lt;ProductTitle /&gt;<br>  &lt;ProductImage /&gt;<br>  &lt;ProductPrice /&gt;<br>  &lt;ProductAttributes /&gt;<br>  &lt;AddToTrolley /&gt;<br>  &lt;AddToFavourites /&gt;<br>&lt;/ProductTile&gt;</pre><p>For each individual component you can good confidence that your experience is accessible using Testing Library. However, something I’ve observed with this pattern is that the combination of:</p><ul><li>Separation of concerns through components; and</li><li>Only introducing accessibility testing through selecting upon specific elements in isolation to each other;</li></ul><p>can lead to a naff user experience where the product name gets announced far too many times:</p><pre>Jaffa Cakes, heading level 2<br>Jaffa Cakes, link<br>Jaffa Cakes, image<br>Current Price of Jaffa Cakes £1.50, Previous Price of Jaffa Cakes £2.00<br>Jaffa Cakes are vegetarian<br>Jaffa Cakes are a best buy<br>Add Jaffa Cakes to trolley, button<br>Add Jaffa Cakes to favourites, button</pre><p>Ok “Jaffa Cakes” is reasonably concise here so you might be forgiven, but if the name of your product or article was longer this would potentially be quite frustrating to navigate through for your users.</p><p>It is worth calling out that users of assistive technologies such as screen readers <strong>don’t just tab through your content</strong> bit by bit— they can navigate by headings, links, controls etc. so some repetition may be required to ensure users who jump straight to “Add to trolley” button have all the context they need – check out the WCAG success criteria <a href="https://www.w3.org/WAI/WCAG22/Understanding/headings-and-labels.html">2.4.6</a> and <a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html">2.4.9</a> for some further reading.</p><p>Where appropriate, you can likely give your users the benefit of the doubt that they can remember the product they have navigated to without reminding them <em>every</em> step of the way.</p><p><em>Of course mileage may vary, so always validate with your own users — your use-case may be different!</em></p><p>So as it stands, Testing Library unfortunately isn’t quite the silver bullet for building these holistic pictures in test automation — for that you need to look elsewhere or to manual testing.</p><blockquote>If you’re interested in how to make tile / card patterns accessible for things like listing products or articles, check out this awesome <a href="https://inclusive-components.design/cards/">Inclusive Components article on Cards</a>.</blockquote><h3>Beyond Testing Library</h3><p>Given some of the shortcomings of Testing Library let’s briefly look at some options for augmenting our test automation.</p><figure><img alt="Two people sat at laptops collaborating on a piece of paper between them with lots of diagrams." src="https://cdn-images-1.medium.com/max/1024/0*FlCBLRED_Vd1KKpF" /><figcaption>Photo by <a href="https://unsplash.com/@homajob?utm_source=medium&amp;utm_medium=referral">Scott Graham</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h4>Getting our hands on the accessibility tree</h4><p>One of the gotchas mentioned above was that Testing Library is having to simulate the accessible role, name, and value calculation because it can’t use browsers’ accessibility APIs to get the actual result.</p><p>One option here is to use the <a href="https://chromedevtools.github.io/devtools-protocol/">Chrome DevTools Protocol (CDP)</a> to query aspects of the accessibility tree. For example in Cypress you could add a command like:</p><pre>Cypress.Commands.add(&quot;getAccessibilityTree&quot;, () =&gt; {<br>  return Cypress.automation(&quot;remote:debugger:protocol&quot;, {<br>    command: &quot;Accessibility.enable&quot;,<br>  }).then(() =&gt; {<br>    return Cypress.automation(&quot;remote:debugger:protocol&quot;, {<br>      command: &quot;Accessibility.getFullAXTree&quot;,<br>    });<br>  });<br>});</pre><blockquote>Credit to <a href="https://medium.com/u/7926e7436229">Paul Grenier</a> for this approach. Check out <a href="https://medium.com/factset/the-automated-ui-testing-methodology-you-need-to-try-9ce4d8afe623">this “Automated UI Testing Methodology You Need To Try” article to explore the idea further.</a></blockquote><p>This would allow you to query or simply snapshot the accessibility tree for your page as a means of testing and validating the actual computed accessible roles and names.</p><p>It also gives you some insight into how your component is seen “as a whole” from the perspective of assistive technology users which may help our Jaffa Cakes verbosity problem from a previous example.</p><p>You can find out more about the Accessibility options on <a href="https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/">the Chrome DevTools Protocol docs</a>.</p><p>Looking to other testing frameworks, the likes of <a href="https://pptr.dev/">Puppeteer</a> and <a href="https://playwright.dev">Playwright</a> both have a built-in accessibility class that allows you to get a snapshot of the accessibility tree in a similar fashion to our hand-rolled Cypress command above:</p><pre>const snapshot = await page.accessibility.snapshot();<br><br>console.log(snapshot);</pre><p>Playwright has deprecated this API in favour of promoting <a href="https://www.deque.com/axe/">Axe</a> for accessibility testing instead — depending on your use-case, this may well make more sense for you.</p><p>You can find out more about the Accessibility class on <a href="https://pptr.dev/api/puppeteer.accessibility">the Puppeteer docs</a> and <a href="https://playwright.dev/docs/api/class-accessibility">the Playwright docs</a>.</p><h4>Using the Accessibility Object Model</h4><p>The <a href="https://github.com/WICG/aom">Accessibility Object Model (AOM)</a> is an emerging JavaScript API that will allow developers to access and modify the accessibility tree for a page.</p><blockquote>This effort aims to create a JavaScript API to allow developers to modify (and eventually explore) the accessibility tree for an HTML page.</blockquote><p>It is currently in early stages with <a href="https://wicg.github.io/aom/spec/">an unofficial draft specification</a> and limitation implementation or usage outside of things like <a href="https://github.com/web-platform-tests/interop-accessibility">Web Platform Tests</a>.</p><p>Nevertheless, this is exciting territory — given this will allow us to access the computed accessible role and name for an element, I can see a future where these APIs get incorporated into the likes of Testing Library!</p><p>If you want to make use of these APIs today in your own projects there are some experimental flags that can get you some of the way. In Chromium there is an <a href="https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/runtime_enabled_features.json5#236">--enable-blink-features=&quot;AccessibilityObjectModel&quot; flag</a> which enables an experimental implementation — tread carefully here as it could be removed any time, the <a href="https://support.apple.com/en-us/101960">equivalent WebKit experimental flag</a> appears to have already been removed.</p><p>Alternatively there is also an --enable-experimental-web-platform-features flag for Chromium browsers that exposes the computedRole and computedName properties on elements which <a href="https://medium.com/u/7926e7436229">Paul Grenier</a> covers in <a href="https://medium.com/factset/the-automated-ui-testing-methodology-you-need-to-try-pt-2-e3be8ba83a44">this “The Automated UI Testing Methodology You Need To Try (Pt. 2)” article</a>.</p><p>At the moment the safest path to follow is likely <a href="https://github.com/web-platform-tests/wpt/blob/master/wai-aria/scripts/aria-utils.js">the approach used in the Web Platform Tests</a> which make use of <a href="https://www.w3.org/TR/webdriver/#dfn-get-computed-role">WebDriver commands</a> (think <a href="https://www.selenium.dev/">Selenium</a>) in their <a href="https://web-platform-tests.org/writing-tests/testdriver.html#test_driver.get_computed_role">test driver</a>, as Chrome, WebKit, and Gecko drivers all have support as of early 2023.</p><p><strong>Some care needed here: </strong>If you explore around you might come across the likes of <a href="https://webdriver.io/docs/api/element/getComputedRole/">getComputedRole() method for WebDriverIO</a>. Unfortunately despite looking like it is using these new APIs, at the time of writing it is actually using the same <a href="https://www.npmjs.com/package/aria-query">aria-query package</a> as Testing Library and so suffers from the same inaccuracies. Take care not to muddle the WebDriver protocol with the WebDriverIO test framework!</p><p>Given this is all very new and experimental, unless you are a test framework maintainer I would recommend relying on the likes of Testing Library for now with the understanding that there may be some role or accessible name inaccuracies — manual testing FTW!</p><h3>A missing puzzle piece?</h3><p>With all the tools we’ve covered in these first three parts we’re starting to be in a really awesome position for building out test automation that can really give us confidence on the accessibility of our applications.</p><p>However, something the tools we’ve explored still can’t help us with is being able to automate tests for asserting on the quality of user experience for those who are using your application with something other than mouse or keyboard.</p><p>Using Testing Library or other packages such as <a href="https://github.com/dmtrKovalenko/cypress-real-events">cypress-real-events</a> can allow us to build reasonably realistic tests that can use mouse or keyboard similar to how real users might, but this still leaves a gap for assistive technologies such as screen readers.</p><p>Imagine you are building out components that require the coordination of multiple elements such as <a href="https://www.w3.org/WAI/ARIA/apg/patterns/combobox/">combobox</a> or <a href="https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/">disclosure</a> patterns, then Testing Library and other tools can only get you so far:</p><ul><li>Static analysis can catch any common gotchas and incorrect syntax.</li><li>Testing Library queries or Accessibility Tree snapshots can give you confidence on usage of the correct accessible roles and names.</li><li><a href="https://www.npmjs.com/package/@testing-library/user-event">@testing-library/user-event</a> and similar packages can give you confidence for mouse and keyboard.</li></ul><p>But nothing other than manual testing can really provide confidence that the combination of a browser + screen reader + your application is being announced and behaving in the expected and intuitive way for a screen reader…</p><p><em>Or so it used to be the case!</em></p><p>In the next part of this series we’re going to start exploring the cutting-edge field of screen reader standardisation and automation, looking into:</p><ul><li>The <a href="https://www.w3.org/community/aria-at/">W3C ARIA and Assistive Technologies (ARIA-AT) Community Group</a>.</li><li>On-demand, remote screen reader testing solutions.</li><li>Virtual and real screen reader test automation with packages such as <a href="https://github.com/guidepup/guidepup">@guidepup/guidepup</a>.</li></ul><p>Till then folks — see you soon! 👋</p><p><em>Hi, my name is </em><a href="https://twitter.com/CraigMorten"><em>Craig Morten</em></a><em>. I am a senior product engineer at the John Lewis Partnership. When I’m not hunched over my laptop I can be found drinking excessive amounts of tea or running around in circles at my local athletics track.</em></p><p>The John Lewis Partnership are hiring across a range of roles. If you love web development and are excited by accessibility, performance, SEO, and all the other awesome tech in web, we would love to hear from you! <a href="https://www.jlpjobs.com/engineering-jobs/">Find our open positions here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4cd590c7395" width="1" height="1" alt=""><hr><p><a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-3-testing-library-and-beyond-f4cd590c7395">Automating A11y Testing: Part 3 — Testing Library and Beyond</a> was originally published in <a href="https://medium.com/john-lewis-software-engineering">John Lewis Partnership Software Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to debug React hydration errors]]></title>
            <link>https://medium.com/@craigmorten/how-to-debug-react-hydration-errors-5627f67a6548?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/5627f67a6548</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Sat, 27 Jan 2024 13:55:21 GMT</pubDate>
            <atom:updated>2024-02-04T19:16:32.639Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<h3>Escaping React Hydration Error Hell</h3><h3>Introduction</h3><p>When hydrating your React applications you might have come across errors that look something like this:</p><figure><img alt="DevTools console error message. Warning: Expected server HTML to contain a matching &lt;button&gt; in &lt;div&gt;." src="https://cdn-images-1.medium.com/max/1024/1*TOhSGEf5wNLRjsa0xbRmUA.png" /></figure><p>This error occurs if there is a difference between the React tree that was rendered server-side versus the React tree that is created client-side during the first render in the browser, as React is unable to reconcile the two differing trees in order to take control over the tree’s UI and interactivity.</p><p>Here we will look into some common causes, how to debug your error, and some general solutions to avoid hydration errors.</p><h3>Causes</h3><p>There are several common causes for hydration errors to be aware of.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5hA9coeKPJvbERdT" /><figcaption>Photo by <a href="https://unsplash.com/@elisa_ventur?utm_source=medium&amp;utm_medium=referral">Elisa Ventur</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h4>Browser-detection based rendering</h4><p>If you have any logic that feature detects if a component is rendering in the browser or on the server, then great care is needed. For example:</p><pre>const isClient = typeof window !== &quot;undefined&quot;;</pre><p>Let’s consider the following contrived example:</p><pre>const IsomorphicContainer = () =&gt; {<br>  const isClient = typeof window !== &quot;undefined&quot;;<br><br>  return isClient ? &lt;ClientOnlyComponent /&gt; : null;<br>};</pre><p>Where we imagine the &lt;ClientOnlyComponent /&gt; can <em>only</em> be used client-side. This is not uncommon when integrating with third parties scripts — perhaps it’s a client-side GTM DataLayer configuration component.</p><p>Issues arise with this code due to React’s understanding of the world being out of sync:</p><ol><li>On the server the isClient condition evaluates to false so a null value is rendered.</li><li>Client-side prior to hydration, window becomes defined meaning this expression would evaluate to true.</li><li>Upon hydration the functional component is rendered for the first time resulting in the &lt;ClientOnlyComponent /&gt; which mismatches with the server-side rendered null value.</li><li>This results in a hydration error being thrown.</li></ol><h4>Breakpoint-detection based rendering</h4><p>As an extension to the browser-detection issues, any component that attempts to feature detect and render conditionally as a result of browser specific APIs can suffer. For example:</p><pre>const mediaQueryList = window?.matchMedia(&quot;(max-width: 768px)&quot;) ?? {};</pre><p>Let’s consider the following contrived example:</p><pre>import { useMediaQuery } from &#39;react-responsive&#39;;<br><br>const BreakpointVaryingCTA = ({ showModel }) =&gt; {<br>  const isMobileOrTablet = useMediaQuery({ maxWidth: 768 });<br><br>  return isMobileOrTablet ? &lt;a href=&quot;/content&quot;&gt;Additional information&lt;/a&gt; : &lt;button type=&quot;button&quot; onClick={showModel}&gt;Additional information&lt;/button&gt;<br>};</pre><p>Here we have a contrived setup where on mobile and tablet devices we are rendering a link, and on desktop devices we are wanting to open a modal for additional information.</p><p>Server-side the media-query hook will evaluate to false because the width server-side is not less than 768px — well in fact there simply isn’t a concept of viewport server-side at all!</p><p>Client-side on desktop we’re lucky because the hook will also resolve to false and we get a hydration match.</p><p>Client-side on mobile we will see issues though. The hook will resolve to true and the first render will result in an anchor element which doesn’t match the button rendered on the server, hence resulting in an error.</p><h4>Whitespace</h4><p>A frustrating and nit-pick cause of hydration errors is whitespace mismatches between the server-side rendered content and the client-side rendered content.</p><p>Consider the following two variations on a React root element created by string interpolation with some template literals:</p><pre>&lt;div id=&quot;root&quot;&gt;${html}&lt;/div&gt;<br> <br>&lt;div id=&quot;root&quot;&gt;<br>  ${html}<br>&lt;/div&gt;</pre><p>The latter example will likely result in a hydration error due to a mismatch between the newlines wrapping the React tree content and the root node:</p><pre>Warning: Did not expect server HTML to contain the text node &quot; &quot; in &lt;div&gt;</pre><p>Similarly if you perform any minification of the response HTML by stripping whitespace you <em>might</em> find that you get similar issues.</p><h4>Data differences</h4><p>If data that is reflected to the user via a component can vary between the server render and client render, either due to timings or the environment, this will also cause a mismatch.</p><p>The most common form of this issue is rendering timestamps:</p><ol><li>Not only will the client-side timestamp not match the server-side one as some time has passed;</li><li>If you server is in a different timezone than the client then without care to ensure timezones are considered in the outputted value you can get differences.</li></ol><p>Other examples can be the likes of:</p><ul><li>Reflecting API data within components that is retrieved prior to hydratation — for example, say you refresh your API data on the client and then hydrate, if the API data has changed since the server-side render then you will get a mismatch.</li><li>Using non-deterministic ids such as using the <a href="https://www.npmjs.com/package/uuid">uuid package</a> on the server and client — instead look to use <a href="https://react.dev/reference/react/useId">React.useId()</a> if you are using React 18 onwards.</li><li>Character encoding differences — ensure your server and client match! Usually utf-8 is a good bet.</li></ul><h4>Invalid HTML</h4><p>Some elements cannot be nesting within other elements. For example you cannot nest an &lt;a&gt; element within another &lt;a&gt; element.</p><figure><img alt="DevTools console error message. Warning: validateDOMNesting(…): &lt;a&gt; cannot appear as a descendant of &lt;a&gt;." src="https://cdn-images-1.medium.com/max/1024/1*VbAj4wxO7dyWULdmuEa2DQ.png" /></figure><p>Depending on the browser such invalid elements may be ejected from the DOM prior to hydration resulting in a mismatch when React tries to hydrate.</p><p>The key here is to write valid HTML!</p><h4>Third party interference</h4><p>There are a couple of known scenarios where mechanics outside of your code can interfere with the server response resulting in hydration issues:</p><ol><li>Browser extensions manipulating the page — see <a href="https://github.com/facebook/react/issues/24430">this React issue</a>.</li><li>Cloud providers / CDNs manipulating the HTML response — see the <a href="https://developers.cloudflare.com/speed/optimization/content/auto-minify/">Cloudflare docs</a>.</li><li>Google Chrome translate function manipulating the page — see <a href="https://github.com/facebook/react/issues/11538">this React issue</a>.</li><li>iOS format detection manipulating the HTML response — see <a href="https://nextjs.org/docs/messages/react-hydration-error#common-ios-issues">the NextJS docs</a>.</li><li>Early running third party scripts, e.g. GTM, HotJar, manipulating the page prior to hydration.</li></ol><h3>Debugging</h3><p>So let’s look at a few ways in which we can debug the root cause of hydration warnings!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AH-XtQ3PJzhgIHMx" /><figcaption>Photo by <a href="https://unsplash.com/@homajob?utm_source=medium&amp;utm_medium=referral">Scott Graham</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h4>React development build</h4><p>When using React’s development build you should receive full, non-minified hydration warnings in your DevTools console that allow you to quickly triage which component has the issue.</p><p>From there you can quickly pattern match your component against some of the known causes of hydration errors and resolve.</p><p>If you are unable to currently run a local version of your application with React in development mode that can integrate with Test or Production environment APIs, it may be worth investing time as a team to introduce this capability for an improvement in developer experience.</p><h4>Log recoverable errors</h4><p>These options assumes you are using React 18 onwards.</p><p><strong>Vanilla React applications</strong></p><p>When calling hydrateRoot() for your React tree there is a third argument you can pass for options.</p><p>One of these options is onRecoverableError which accepts a callback to invoke whenever React automatically recovers from an error, for example a hydration error.</p><p>It is recommended that you consider adding instrumentation to this callback so that you are able to log these issues to your observability platform, e.g. New Relic, Sentry, Datadog, Elastic RUM etc.</p><p>Here is an example snippet:</p><pre>import { hydrateRoot } from &#39;react-dom/client&#39;;<br>import MyObservabilityPlatform from &#39;my-observability-platform&#39;;<br>import App from &#39;./App&#39;<br> <br>function onRecoverableError(error, errInfo) {<br> let context = {};<br> <br>  if (errInfo?.componentStack) {<br>     // Generating this synthetic error allows monitoring services to apply sourcemaps<br>     // to unminify the stacktrace and make it readable.<br>     const errorBoundaryError = new Error(error.message);<br>     errorBoundaryError.name = `React ErrorBoundary ${errorBoundaryError.name}`;<br>     errorBoundaryError.stack = errInfo.componentStack;<br> <br>     // REF: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause<br>     error.cause = errorBoundaryError;<br> <br>     // You can also just add the plain text as added event context.<br>     context.componentStack = errInfo.componentStack;<br>  }<br> <br>  // Replace with your error monitoring service.<br>  MyObservabilityPlatform.captureException(error, { context })<br>}<br> <br>const domNode = document.getElementById(&#39;root&#39;);<br> <br>hydrateRoot(domNode, &lt;App /&gt;, { onRecoverableError });</pre><blockquote>Credit for the code snippet to the developers at <a href="https://sentry.io/">Sentry</a>, see <a href="https://github.com/facebook/react/issues/26224">this React issue for the discussion</a>.</blockquote><p>More information on the hydrateRoot() options are available on the <a href="https://react.dev/reference/react-dom/client/hydrateRoot#hydrateroot">React docs</a>.</p><p><strong>NextJS applications</strong></p><p>When using NextJS you are not responsible for invoking the hydration of the application, this is done for you by NextJS.</p><p>NextJS does not currently expose the ability to pass an onRecoverableError option to hydrateRoot(). Follow <a href="https://github.com/vercel/next.js/discussions/36641">https://github.com/vercel/next.js/discussions/36641</a> for discussion on attempts to get this option exposed.</p><p>Nevertheless we can manually patch the option to be able to debug issues both locally and in Production. One way of doing this is using Chrome Local Overrides:</p><ol><li>In Chrome, navigate to the page with the hydration errors and open DevTools.</li><li>Open the <strong>Sources</strong> tab and within the left side menu choose the <strong>Page</strong> tab. Right hand click on the asset tree to reveal a <strong>Search in all files</strong> option.</li><li>In the pane that opens, search for the term <strong>onRecoverableError</strong> to reveal all code matches.</li><li>One of the code hits should be something similar to this.onRecoverableError = a;. We will be patching this line to instead log both the error and the errorInfo arguments.</li><li>Right hand click on the file tab and click the <strong>Override Content</strong> option. This should open up the <strong>Overrides</strong> tab on the side menu. Ensure the <strong>Enable Local Overrides</strong> option is checked.</li><li>If the file appears minified at this point, use the prettifier button {} to make the code more readable.</li><li>Replace the code with the following snippet:<br>this.onRecoverableError = (error, errorInfo) =&gt; { console.error(error, errorInfo); }. This will ensure that both the hydration error and the additional componentStack information is now logged to the console. Make sure you save!</li><li>Refresh the page and observe that you should now have an additional object logged alongside the hydration errors. You can now use the component stack to triage which component has the issue. Because the stack is minified it may be difficult to identify exactly what components are in play — it is often useful to start from an identifiable element such as a main tag and then follow the tree into the children.</li></ol><p>An alternative strategy to manually patching is to patch in code, see <a href="https://github.com/vercel/next.js/discussions/36641#discussioncomment-5215494">this NextJS discussion thread</a> or this <a href="https://github.com/AbhiPrasad/nextjs-hydration-error-example/blob/cc87bb1b477b49abbc18262f1023a47f2f971331/sentry.client.config.js#L12-L38">code example</a> for ideas on how to patch NextJS. This is not advised for Production environments, but might be a convenient capability to introduce for local development builds for improved developer experience.</p><h4>DevTools debugger</h4><p>Another way to identify hydration errors can be to use the DevTools debugger to pause on exceptions, particularly “Pause on caught exceptions” can be particularly useful for hydration errors.</p><p>The downside to this approach is that it can be quite laborious to identify the cause depending on how many exceptions your application raises (and catches). You might you need to step through quite a number of irrelevant caught exceptions before you encounter something relevant to your issue.</p><p>More information on caught exceptions for Chrome DevTools can be found in the <a href="https://developer.chrome.com/docs/devtools/javascript/breakpoints/#exceptions">Chrome developer docs</a>.</p><h3>Solutions</h3><p>Assuming reconciling what is rendered server and client-side is not an option there are few tactics for working around hydration issues.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9n9NciNA5g4vISFt" /><figcaption>Photo by <a href="https://unsplash.com/@clemhlrdt?utm_source=medium&amp;utm_medium=referral">Clément Hélardot</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h4>Defer to second render</h4><p>One workaround is to ensure that the same content is rendered server-side and client-side for the first render, and then from the second render onwards we can render the client-side specific components.</p><pre>const IsomorphicContainer = () =&gt; {<br>  const [isClient, setIsClient] = useState(false);<br> <br>  useEffect(() =&gt; {<br>    setIsClient(true);<br> <br>    return () =&gt; {<br>      setIsClient(false);<br>    }<br>  }, []);<br> <br>  return isClient ? &lt;ClientOnlyComponent /&gt; : null;<br>}</pre><p>Effects don’t execute server-side so the isClient value is false when rendered on the server.</p><p>Effects also run after the first render, so on the first pass client-side the isClient boolean is still false and we have a hydration match.</p><p>The mounting of the component to the React tree then triggers the side effect which toggles the boolean to true and yields the &lt;ClientOnlyComponent /&gt;.</p><p>More information is available on <a href="https://react.dev/reference/react-dom/client/hydrateRoot#handling-different-client-and-server-content">the React docs</a>.</p><p><strong>Additional considerations</strong></p><p><strong><em>Performance</em></strong></p><p>This workaround results in the component rendering twice whenever it is mounted to the React tree which has a performance overhead compared to simply rendering once when hydrating.</p><p>The additional CPU time spent re-rendering the component can have a negative impact on CPU bound performance metrics such as <a href="https://web.dev/articles/lcp">Largest Contentful Paint (LCP)</a> and <a href="https://web.dev/articles/inp">Interaction to Next Paint (INP)</a>.</p><p><strong><em>Flash of unstyled content</em></strong></p><p>In the case of components where something is rendered visually, if you are encountering a Flash of unstyled content (FOUC) with this approach consider using useLayoutEffect as this will trigger prior to the browser repainting the screen.</p><p>This ensures the entire cycle of first render, hook firing, and second render all happens prior to the first repaint to the screen.</p><p>This does incur a performance impact on the time to next paint so use of useEffect should always be considered first, especially as metrics such as INP become more prevelant.</p><h4>NextJS dynamic package “magic”</h4><p>If you are using NextJS there are some alternative options to specify that a component should be considered client-side only.</p><p>This is through making use of the <a href="https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic">next/dynamic module</a>:</p><pre>import dynamic from &#39;next/dynamic&#39;;<br> <br>const ClientOnlyComponent = dynamic(() =&gt; import(&#39;../components/Component&#39;), { ssr: false });</pre><p>More information is available on <a href="https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading#skipping-ssr">the NextJS docs</a>.</p><h4>Loadable components package “magic”</h4><p>If you are using the <a href="https://www.npmjs.com/package/@loadable/component">@loadable/component package</a> there is an option to specify that a component should be considered client-side only, similar to the next/dynamic package:</p><pre>import loadable from &#39;@loadable/component&#39;;<br><br>// This dynamic import will not be processed server-side<br>const Other = loadable(() =&gt; import(&#39;../components/Component&#39;), { ssr: false });</pre><p>More information is available on <a href="https://loadable-components.com/docs/server-side-rendering/#disable-ssr-on-a-specific-loadable">the Loadable components docs</a>.</p><h4>Multiple rendering</h4><p>When tackling scenarios such as the breakpoint based rendering, you can opt to use the “defer to second render” technique, but this will almost always result in at least one of your breakpoints having a flash of content that is meant for a different viewport before the correct components get rendered on second pass.</p><p>This can be mitigated through CSS by visually hiding the content altogether until the second render, but this will likely have a negative impact on your <a href="https://web.dev/articles/cls">Content Layout Shift (CLS)</a> metric which impacts Search Engine Optimisation (SEO) and also lends itself to a poor user experience.</p><p>The generally accepted solution here is to instead render <em>all </em>of the potential variations server-side:</p><ol><li>Server-side render all of the variations required across the breakpoints.</li><li>Use CSS media queries delivered in a render blocking style or link tag that ensures only the desired variation is displayed to users on the client when first loading.</li><li>Use the “defer to second render” technique to ensure there are no hydration mismatches.</li><li>On second render you can have the undesired components render null and unmount themselves.</li></ol><p>Although this does result in an inflated server rendered HTML that might impact your <a href="https://web.dev/articles/ttfb">Time to First Byte (TTFB)</a>, and it also suffers from the CPU usage caveats of the “defer to second render” technique, it ensures that there is a seamless transition between server and client without any flashes of undesired content.</p><p>More information on this technique can be found in this article: <a href="https://medium.com/@craigmorten/eliminating-cls-when-using-ssr-for-viewport-specific-responsive-designs-2d5ab0d05bfa">Eliminating CLS when using SSR for viewport specific responsive designs</a>.</p><h4>React tree pruning</h4><blockquote>This is an advanced solution that is applicable to breakpoint based rendering, tred carefully!</blockquote><p>The idea here is similar to the “multiple rendering” technique, but instead of allowing multiple variations to all be hydrated and then removed on second render, we attempt to prune the React tree before / as we hydrate the tree:</p><ol><li>Render all variations server-side.</li><li>Use CSS to ensure no flash of undesired content.</li><li>On hydrate / first render of variations we determine if the variation is desired, and if not, we prune it from the DOM before React get’s a hold of it — React can’t mismatch on a DOM node that it doesn’t know ever existed.</li><li>Because all that remains in the DOM is the only valid variation we don’t need to worry about using the “defer to second render” technique, we can simply render what we want immediately.</li></ol><p>Similar to the “multiple rendering” technique this does also result in an inflated server rendered HTML that might impact your TTFB, but it doesn’t suffer from any of the other performance impacts of other techniques.</p><p>More information on this technique and how to implement can be found in <a href="https://gist.github.com/OliverJAsh/e9a588e7e907101affe1a7696a25b1fd">this gist example</a> and <a href="https://github.com/artsy/fresnel/pull/341">this pull request</a> for the <a href="https://www.npmjs.com/package/@artsy/fresnel">@artsy/fresnel package</a>.</p><p>You can also check out <a href="https://twitter.com/sebastienlorber/status/1742528219318738955">recent discussion on X</a> (formally known as Twitter).</p><h3>Seb ⚛️ ThisWeekInReact.com on Twitter: &quot;🤯 React hydration perf trickPermits to avoid hydrating a mobile component on a desktop (or the opposite)Better than hydrating both and hiding one with CSS 👎How it works? Deletes the useless DOM element before hydration.Is this even ok to do so? https://t.co/q53ZOaodDq pic.twitter.com/aWOpQe8vyA / Twitter&quot;</h3><p>🤯 React hydration perf trickPermits to avoid hydrating a mobile component on a desktop (or the opposite)Better than hydrating both and hiding one with CSS 👎How it works? Deletes the useless DOM element before hydration.Is this even ok to do so? https://t.co/q53ZOaodDq pic.twitter.com/aWOpQe8vyA</p><h3>Disable hydration warnings</h3><p>HTML element JSX accepts a suppressHydrationWarning boolean in React that can be used to supress warnings arising from hydration errors.</p><p>This should only be used as a last resort in scenarios where the data cannot be reconciled, for example when writing timestamps or dates to the page, and only when the differences being masked are in text content.</p><p>More information is available on <a href="https://react.dev/reference/react-dom/client/hydrateRoot#suppressing-unavoidable-hydration-mismatch-errors">the React docs</a>.</p><p>That’s it folks! Thanks for reading ☺️</p><p>Have any other examples that result in hydration errors? Or have any handy ways to debug or fix issues?</p><p>Let me know in the comments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5627f67a6548" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automating a11y testing: Part 2— Beyond Axe]]></title>
            <link>https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-2-beyond-axe-af0f32f366e7?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/af0f32f366e7</guid>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[a11y]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[accessibility-testing]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Wed, 05 Jul 2023 15:04:54 GMT</pubDate>
            <atom:updated>2023-07-05T15:04:54.189Z</atom:updated>
            <content:encoded><![CDATA[<p>Earlier this year I gave a talk on the topic of automating web accessibility (a11y) testing at the John Lewis Partnership Tech Profession Conference 2023 which I’m delighted to be sharing here in an article series format.</p><p>In <a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126">Part 1</a> of this series I covered a number of tools from the <a href="https://www.deque.com/axe/">Axe suite</a> for static analysis of sites to find a11y violations, from framework specific packages to VSCode integrations, and sharing a few learnings with caveats and gotchas from using Axe tools.</p><p>However, this is just a small subset of available test automation tools. In this article we will start to explore some of the non-Axe tooling (though Axe will certainly pop up again!) in the wider a11y test automation space. Let’s get going!</p><h3>Linting for a11y</h3><p>Although some aspects of web accessibility require manual validation as they are subjective regarding the user experience, many requirements of the <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">Web Content Accessibility Guidelines (WCAG)</a> are for ensuring you use the correct semantic markup and associated attributes to correctly present your content to users. Such requirements are well suited to static analysis checks as they are deterministic and rule based — indeed there is such a set of official rules set out in the <a href="https://www.w3.org/WAI/standards-guidelines/act/">Accessibility Conformance Tests (ACT) specification</a> which are used by static analysis tools such as Axe that we covered in <a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126">Part 1</a>.</p><blockquote>“Throughout this series I will be mostly focusing on the automation side of a11y testing, but it is also key to remember the importance of manual validation and exploratory testing” — Key considerations from <a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126">Part 1</a> still apply!</blockquote><p>Although we can run static analysis checks in CI, as a form of push left and also early feedback, a really natural and effective place to run these checks is in code linting. What is linting if not static analysis of code / markup! We’ve already seen the <a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126#2279">VSCode Axe Linter</a> in the previous article, let’s look at some others.</p><h4><strong>Eslint</strong></h4><p>If you are already plugged into the <a href="https://eslint.org/">Eslint</a> ecosystem, there are a number of plugins that you can reach for depending on your framework:</p><ul><li>React and other JSX based frameworks — <a href="https://www.npmjs.com/package/eslint-plugin-jsx-a11y">eslint-plugin-jsx-a11y</a></li><li>React Native — <a href="https://github.com/FormidableLabs/eslint-plugin-react-native-a11y">eslint-plugin-react-native-a11y</a></li><li>Vue — <a href="https://github.com/maranran/eslint-plugin-vue-a11y">eslint-plugin-vue-a11y</a> and / or <a href="https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility">eslint-plugin-vuejs-accessibility</a></li><li>Angular — <a href="https://www.npmjs.com/package/@angular-eslint/eslint-plugin">@angular-eslint/eslint-plugin</a></li><li>Lit Web Components — <a href="https://www.npmjs.com/package/eslint-plugin-lit-a11y">eslint-plugin-lit-a11y</a></li></ul><p>Mileage may vary with coverage from plugin to plugin, but each comes with the same great developer experience of real time feedback <em>as</em> you write your code as well as all the other awesome stuff you get with using Eslint such as IDE integrations with intellisense, auto-fix, etc.</p><figure><img alt="VSCode tab open on a “ProductPod Slidedown” React component. In the component a div element is being rendered with a number of event handler props for mouse enter, click, and key down. The entire div JSX is underlined in yellow and there is a hover tooltip with a warning reading: “Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element.”" src="https://cdn-images-1.medium.com/max/1024/1*Y7Z3XCk4i3Kbym-tsFRy_Q.png" /><figcaption>A warning from <a href="https://www.npmjs.com/package/eslint-plugin-jsx-a11y">eslint-plugin-jsx-a11y</a> in VSCode</figcaption></figure><p>Same as your other linting rules, you can also lean into the power of running the linting both locally, whether manually or through git hooks, as well as in CI to ensure you catch issues before you even consider raising that pull request.</p><h4>Svelte</h4><p>Those reading closely may have noticed that the Eslint plugin list above (which was by no means exhaustive) was missing the popular framework Svelte.</p><blockquote>“SvelteKit strives to provide an accessible platform for your app by default. Svelte’s <a href="https://svelte.dev/docs#accessibility-warnings">compile-time accessibility checks</a> will also apply to any SvelteKit application you build.” — <a href="https://kit.svelte.dev/docs/accessibility">https://kit.svelte.dev/docs/accessibility</a></blockquote><p>Svelte (and SvelteKit) have a number of <a href="https://svelte.dev/docs/accessibility-warnings">accessibility warnings</a> built into the compiler itself which cover a subset of a11y rules. It’s super cool to have these built into the framework itself, but it is worth flagging that these aren’t a catch-all. Even within the limited scope of static analysis checkers, <a href="https://twitter.com/geoffrich_/status/1383170662651482115?s=20">as flagged by one of the Svelte maintainers Geoff Rich</a>, there are a number of gotchas around these compiler checks.</p><p>For example, the Svelte compiler is only able to check hard-coded values in markup. If you provide a dynamic value via a variable the Svelte compiler isn’t able to determine the possible values for that attribute and thus doesn’t yeild the desired warning.</p><pre>&lt;script&gt;<br>    let href = &#39;#&#39;;<br>&lt;/script&gt;<br><br>&lt;a href={href}&gt;More Information&lt;/a&gt;</pre><p>In this example the href=&quot;#&quot; should normally triggers an a11y warning from the compiler, but using the variable results in the warning being surpressed.</p><h4>Other</h4><p>There are a limited number of other linting options out there for a11y, some of which are paid for options:</p><ul><li><a href="https://www.npmjs.com/package/ember-template-lint">ember-template-lint</a> — A linter for Ember projects that includes a11y rules.</li><li><a href="https://github.com/marketplace/accesslint">AccessLint </a>— A paid for (with free trial) GitHub App Integration: “AccessLint brings automated web accessibility testing into your development workflow.”</li><li><a href="https://www.deque.com/axe/devtools/linter/">Axe DevTools Linter</a> — In addition to the VSCode plugin covered in Part 1, Axe also have paid for (with free trial) integrations with GitHub Actions, SonarQube, a REST API, and a CLI for their a11y linter.</li></ul><h3>Visual testing</h3><p>Let’s pivot over to some ideas around a11y visual testing!</p><h4>Emulated vision deficiencies</h4><p>Back in Chrome 83 the Chromium team released a DevTools change that allowed you to <a href="https://developer.chrome.com/blog/new-in-devtools-83/#vision-deficiencies">emulate different vision deficiencies</a> in the browser. This is accessible from the <a href="https://developer.chrome.com/docs/devtools/command-menu/">Chrome DevTools Command Palette</a> (open DevTools and key CMD+SHIFT+P on Mac or CTRL+SHIFT+P for Windows) by searching for “Rendering”, where you can then pick from six different emulated vision deficiencies.</p><figure><img alt="Chrome open on Waitrose №1 Dine In Meal Deal web page. Chrome DevTools is open on the “Rendering” drawer. Under “Emulate vision deficiencies” the “Blurred vision” option has been selected in the dropdown." src="https://cdn-images-1.medium.com/max/1024/1*-Jxv80wsLzSQ8l1zr1lxbA.png" /></figure><p>Something that I’ve learned recently is that Chrome also boasts a <a href="https://chromedevtools.github.io/devtools-protocol/">Chrome DevTools Protocol (CDP)</a> that allows you to set almost every feature you get in DevTools via an API. For example, for those using Playwright this example demonstrates how we can set up a CDP session and request to set an emulated blurred vision:</p><pre>type EmulatedVisionDeficiency =<br>  | &#39;none&#39;<br>  | &#39;blurredVision&#39;<br>  | &#39;reducedContrast&#39;<br>  | &#39;achromatopsia&#39;<br>  | &#39;deuteranopia&#39;<br>  | &#39;protanopia&#39;<br>  | &#39;tritanopia&#39;;<br><br>test(&#39;visually acceptable when have blurred vision&#39;, async ({ page }) =&gt; {<br>  const client = await page.context().newCDPSession(page);<br><br>  await client.send(&#39;Emulation.setEmulatedVisionDeficiency&#39;, {<br>    type: &#39;blurredVision&#39;,<br>  });<br><br>  // ... visual test with Playwright / other third party lib<br>});</pre><p>Provided your test framework supports a Chromium based browser then CDP should be available and you can make use of this capability in your based visual tests, e.g. using <a href="https://playwright.dev/docs/test-snapshots">Playwright screenshots</a> or <a href="https://docs.cypress.io/guides/tooling/visual-testing">Cypress image snapshots</a>.</p><p>For example, here is emulated achromatopsia (where you can’t perceive colour) for a snapshot test on John Lewis Women’s Dresses product listing page (PLP):</p><figure><img alt="VSCode window with two tabs open. One containing Playwright tests for setting up different vision deficiencies via CDP and then using the Playwright screenshot API. The other tab shows the resulting screenshot for the achromatopsia test — a greyscale image of the John Lewis Women’s Dresses product listing page." src="https://cdn-images-1.medium.com/max/1024/1*-CJlRbC_mCG8M07RMi2spA.png" /></figure><p>Here’s the same PLP from the blurred vision test:</p><figure><img alt="The John Lewis Women’s Dresses product listing page as viewed by a user with blurred vision." src="https://cdn-images-1.medium.com/max/1024/1*rlJbjYw6fvWyx3DpukfTLA.png" /></figure><p>And finally one from the protanopia (where you can’t perceive Red light) test:</p><figure><img alt="The John Lewis Women’s Dresses product listing page as viewed by a user with protanopia." src="https://cdn-images-1.medium.com/max/1024/1*K0NwIRn7RvpCdKJ0KbGbsw.png" /></figure><p>I wouldn’t recommend going overboard with running all of these variations for every test — visual tests are typically heavy and slow by nature, and fragile to browser upgrades, anti-aliasing, and other false positive issues (although this is somewhat mitigated through using third party vendors who typically have intelligent diffing algorithms).</p><blockquote>Mileage may vary with third part integrations — if your provider performs the snapshot on the browser instance you’re running (locally or in CI) then this may well be an option, but if they do HTML + CSS snapshotting to recreate the page in different browsers on their servers then this is likely not a goer.</blockquote><p>It could be worth considering adding to a couple to your suites for golden path smoke tests. Alternatively, it can be nice to have such tests running in production on a nightly, or weekly, where the snapshots form the basis of design review discussion opposed to a blocking gate in release.</p><blockquote>“In the UK, more than 2 million people are living with sight loss. Of these, around 340,000 are registered as blind or partially sighted.” — <a href="https://www.nhs.uk/conditions/vision-loss/">https://www.nhs.uk/conditions/vision-loss/</a></blockquote><blockquote>“Globally, at least 2.2 billion people have a near or distance vision impairment.” — <a href="https://www.who.int/news-room/fact-sheets/detail/blindness-and-visual-impairment">https://www.who.int/news-room/fact-sheets/detail/blindness-and-visual-impairment</a></blockquote><blockquote>“Colour blindness (colour vision deficiency, or CVD) affects approximately 1 in 12 men (8%) and 1 in 200 women in the world. In Britain this means that there are approximately 3 million colour blind people (about 4.5% of the entire population), most of whom are male. Worldwide, there are approximately 300 million people.” — <a href="https://www.colourblindawareness.org/">https://www.colourblindawareness.org/</a></blockquote><p>The key is to be aware and empathetic in your design and implementation towards folks who have visual impairments, and understanding of their experience of your site. A few extra, simple tests can help bring colour (pun very much intended) to otherwise dry requirements around font-size and colour contrast which are hard to appreciate in isolation — a quick change to a base font-size in your design system could be a game change for some folks experience on your site.</p><h4>Automating zoom</h4><p>Another important aspect of visual accessibility is supporting higher zoom levels, typically of up to 200%. This is laid out in several success criteria in WCAG for text resize and content reflow.</p><blockquote>“This Success Criterion helps people with low vision by letting them increase text size in content so that they can read it.” — <a href="https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html">https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html</a></blockquote><p>Unfortunately most major frameworks don’t support APIs to instrument browser zoom at the moment. Searching around you might see references to the --force-device-scale-factor=2.0 device scale factor flag for Chrome, but this relates to pixel density and is not for zoom (can be passed to launchOptions.args array within your framework’s browser setup options if you want to try and see).</p><p>This limitation has been flagged for some testing frameworks so we can hope it might be supported in future. For now you can upvote the likes of <a href="https://github.com/microsoft/playwright/issues/2497">this Playwright issue for adding an option for browser zoom</a>.</p><p>While we wait for frameworks to catch up, the best solution that I’ve found for the time being is to simulate zoom by adding a before hook in your tests which sets the zoom style of the body. For example, this is a snippet from a Playwright visual test in which the a 200% zoom is applied through CSS:</p><pre>await page.evaluate(&#39;document.body.style.zoom=2.0&#39;);</pre><figure><img alt="Waitrose The King’s Coronation Jewel The Jack Russell Cake product detail page with 200% zoom on desktop." src="https://cdn-images-1.medium.com/max/1024/1*1ueqotHdWdvwnlqSNrhUWQ.png" /></figure><p>In many cases this isn’t necessarily representative of using the native browser zoom feature, nor the OS level zoom features for enlarging text, which is what the WCAG guidance is really for. Nevertheless, it somewhat provides an idea as to how your page behaves when magnified, and can be a useful addition to your golden path test suite to understand if critical functionality still works.</p><p>If anything this goes to show how important it is to keep up manual and exploratory testing when it comes to a11y, there is no way to get the coverage otherwise! When it comes to vision deficiencies we are really still quite limited on how much we can shift to automation — some smoke tests on Chrome are great, but what about other browsers? What about tablet and mobile?!</p><p>If anyone has any ideas on automation in this space I would love to hear them!</p><h3>Time to get funky</h3><p>In this last section I wanted to share one last tool called <a href="https://www.funkify.org/">Funkify</a>.</p><blockquote>“Funkify is an extension for Chrome that helps you experience the web and interfaces through the eyes of extreme users with different abilities and disabilities.” — <a href="https://www.funkify.org/">https://www.funkify.org/</a></blockquote><p>It is questionably accessibility test automation, as it is a browser extension… but I’ve decided it qualifies as something that automates customer personas to supplement your exploratory testing. It’s also just very cool so worth the share on that premise alone.</p><p>Because it is rather good unfortunately (but quite understandably) the maintainers have introduced a premium level around a lot of the functionality. But even if you just have a dabble with the free features I think it’s well worth it for expanding horizons.</p><figure><img alt="Waitrose groceries homepage open in a Chrome browser. In the toolbar the Funkify extension has been activated, displaying a popup with a number of personna simulators that can be selected: “Blurry Bianca, Color Carl, Dyslexia Dani, Trembling Trevor, Tunnel Toby, Peripheral Pierre”." src="https://cdn-images-1.medium.com/max/1024/1*COcasHsknmhnKGAUS-QOHQ.png" /></figure><p>The extension works well on most sites, and as you can see, provides a series of simple personas or modes that you can adopt while browsing the site. Everything from vision defects to simulated dyslexia with scrambling letters to trembling hands.</p><p>For example, here is the Waitrose groceries landing page with the Dyslexia Dani persona example where we can get a flavour of what the user experience might be like (be wary this is an example simulation, it is not necessarily representative of the experience for all folks with Dyslexia):</p><figure><img alt="Waitrose groceries homepage open in a Chrome browser. In the toolbar the Funkify extension has been activated, displaying a popup where the “Dyslexia Dani” persona has been activated. The letters in the text on the Waitrose page is slightly jumbled." src="https://cdn-images-1.medium.com/max/1024/1*RSUwzgqRLdXBi2O-o5LKLg.png" /></figure><p>One option that really struck home for me is the Trembling Trevor persona which simulates how it might be like to use the site if you suffer from motor degenerative disorders such as Parkinson’s. Unfortunately Medium doesn’t support embedding videos directly to show this off, so I really encourage giving it a go with the free trial at least once! Once you’ve had a play with the extension on desktop, have a think about touch devices — ask yourself how confident are you with your site’s buttons, links, and dropdowns being usable on a small touch screen?</p><h3>Closing notes</h3><p>In this article I’ve covered off a few additional tools and techniques for expanding a11y coverage, exploring extensions to the likes of visual testing and how this can supplement manual and exploratory testing.</p><p>This article hasn’t been extensive by far, but hopefully a flavour of what tooling is out there. If you’re keen to explore further, these are some awesome sites where you can learn more and find other tools in the a11y automation space:</p><ul><li><a href="https://www.a11yproject.com/resources/">https://www.a11yproject.com/resources/</a> — absolute wealth of informationa and resources, from blogs and books to tools and organisations.</li><li><a href="https://a11y-automation.dev/automated-tools">https://a11y-automation.dev/automated-tools</a> —a comprehensive list of automated tools for a11y testing.</li><li><a href="https://www.w3.org/WAI/ER/tools/">https://www.w3.org/WAI/ER/tools/</a> — a list of evaluation tools for web accessibility as curated by W3.</li></ul><p>Stay tuned for part 3 of this series where we will take a look at the popular “accessibility first” <a href="https://testing-library.com/">Testing Library</a> framework and start to explore the emerging field of screen reader automation tooling. See you soon! 👋</p><p><em>Hi, my name is </em><a href="https://twitter.com/CraigMorten"><em>Craig Morten</em></a><em>. I am a senior product engineer at the John Lewis Partnership. When I’m not hunched over my laptop I can be found drinking excessive amounts of tea or running around in circles at my local athletics track.</em></p><p>The John Lewis Partnership are hiring across a range of roles. If you love web development and are excited by accessibility, performance, SEO, and all the other awesome tech in web, we would love to hear from you! See our open positions <a href="https://www.jlpjobs.com/engineering-jobs/">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=af0f32f366e7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-2-beyond-axe-af0f32f366e7">Automating a11y testing: Part 2— Beyond Axe</a> was originally published in <a href="https://medium.com/john-lewis-software-engineering">John Lewis Partnership Software Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Eliminating CLS when using SSR for viewport specific responsive designs]]></title>
            <link>https://medium.com/@craigmorten/eliminating-cls-when-using-ssr-for-viewport-specific-responsive-designs-2d5ab0d05bfa?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/2d5ab0d05bfa</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[web-design]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[seo]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Fri, 30 Jun 2023 18:23:36 GMT</pubDate>
            <atom:updated>2024-02-02T12:01:38.388Z</atom:updated>
            <content:encoded><![CDATA[<h3>How to eliminate layout shifts when doing SSR in React</h3><p>A common gotcha when building some responsive sites is that the desired designs for different viewports are sometimes quite different. In many cases, utilising <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries">CSS media queries</a> is sufficient to be able to cater for these differences, but there are occasions where this isn’t necessarily sufficient on it’s own. It is also not always a pragmatic approach to refuse to implement such designs!</p><figure><img alt="A stack of engineering books, a phone, and a plant. The top book is titled  “Stunning CSS”." src="https://cdn-images-1.medium.com/max/1024/0*zXsicteUz8C1LnsK" /><figcaption>Photo by <a href="https://unsplash.com/@kobuagency?utm_source=medium&amp;utm_medium=referral">KOBU Agency</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>CSS only gets you so far</h3><p>For example, take the following markup for a ficticious article component where on desktop information about the author is contained within a slidedown from the top of the component, and on mobile it is inside a static section at the bottom:</p><pre>&lt;Article&gt;<br>  &lt;ArticleAuthorInformationDesktop /&gt;<br>  &lt;ArticleImage /&gt;<br>  &lt;ArticleHeading /&gt;<br>  &lt;ArticlePreviewText /&gt;<br>  &lt;ArticleAuthorInformationMobile /&gt;<br>&lt;/Article&gt;</pre><p>Now if the markup used for the “top” desktop and the “bottom” mobile author information components were exactly the same you could instead opt to make use of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout">CCS grid</a> or the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Ordering_flex_items">CSS flexbox order</a> to simply have the component once in markup, and use CSS media queries to set the appropriate CSS rule to position the component as desired for the different viewports.</p><p>However, this comes with caveats:</p><blockquote>“Use of the order property has exactly the same implications for accessibility as changing the direction with flex-direction. Using order changes the order in which items are painted, and the order in which they appear visually. It does not change the sequential navigation order of the items. Therefore if a user is tabbing between the items, they could find themselves jumping around your layout in a very confusing way.” — <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Ordering_flex_items#the_order_property_and_accessibility">https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Ordering_flex_items#the_order_property_and_accessibility</a></blockquote><p>If we were to be clever here and try to achieve the desired differences in layout entirely with CSS, we would be introducing potential accessibility (a11y) issues as the experience for visual and non-visual users would differ.</p><p>In theory this can be worked around with either clever use of <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex">tabindex</a> and / or <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-owns">aria-owns</a> where in either case you can identify the order in which elements are expected to be traversed — however both cases are almost always strongly advised against. It is risky to try to hijack the default tab order of a page, and <a href="https://a11ysupport.io/tech/aria/aria-owns_attribute">aria-owns is not supported by VoiceOver</a> meaning it’s use would only resolve the issue for non fruit related devices. Both options are incredibly fragile to changes to associated components where there is a real maintenance overhead to ensuring this is never regressed.</p><p>This also all falls apart if the desktop and mobile versions of the component need to be completely different, e.g. a slidedown vs just a static content section — you can’t use CSS to change HTML markup! This means you will almost certainly be needing different components and then either not rendering or hiding the one that doesn’t fit the viewport.</p><h3>Server-side rethinkings</h3><p>This is fine right? Well, it is until you start server-side rendering (SSR) this article component. The server doesn’t have a concept of a viewport out of the box, so there is no way to know which of the two variations will need to be in the HTML response that you’re sending to your users’ browsers.</p><figure><img alt="Laptop with a CSS file opened in a code editor." src="https://cdn-images-1.medium.com/max/1024/0*X2PBBZt9RT5YC6kK" /><figcaption>Photo by <a href="https://unsplash.com/@jantined?utm_source=medium&amp;utm_medium=referral">Jantine Doornbos</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In the absence of viewport details on the server when rendering, this means:</p><ol><li>You can guess (ahem, I mean “adopt mobile-first”), but this will inevitably result in some sort of <a href="https://en.wikipedia.org/wiki/Flash_of_unstyled_content">flash of unstyled content (FOUC)</a> or <a href="https://web.dev/cls/">cumulative layout shift (CLS)</a> whenever you guess wrong, which is a bit naff!</li><li>You can choose to not render either server-side, but then you will almost definitely get a CLS when the component is hydrated client-side, or best case it will pop in late which is annoying.</li><li>You can choose to render both, but then there’s the same issues as above where one of the variations is there on page load and then get’s removed shortly after either looking visually poor and potentially causing a CLS.</li></ol><p>This isn’t necessarily a new nor unsolved problem. Aside from the options above, one idea is to make use of the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints">Client Hints</a> to provide your server with context of where this component will be rendered so an appropriate choice can be made. Alternatively a similar flow can be achieved with <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent">“user-agent sniffing”</a> through packages such as <a href="https://github.com/faisalman/ua-parser-js">ua-parse-js</a> where you detect the user-agent header and from that deduce a likely device type and size.</p><blockquote>Be sure to take care that you include the source of the hint in any cache-keys you might have otherwise you might cache poison if you render differently for different viewports!</blockquote><p>Lastly, perhaps my preferred solution is (1) to use one of the above hint techniques if possible (and reliable) and then (2) do the following:</p><ol><li>If you have a trusted hint then render only what is needed in your HTML and stop here.</li><li>Otherwise server-side render both variations of the component in your HTML response.</li><li>In addition to this double render, add a small snippet of CSS and appropriate styles / classes to the components with CSS media queries ensuring that client-side they are only displayed if the viewport matches.</li><li>Upon hydration you can let JS take over, remove the component from the DOM which isn’t required for the viewport and strip the “SSR smoothing over CSS” attributes as they are no longer required.</li></ol><p>If you want to see an example implementation of this idea check out the <a href="https://www.npmjs.com/package/@artsy/fresnel">@artsy/fresnel</a> package (and I’m sure there are others).</p><h3>How can I do this myself?</h3><p>It’s not always appropriate to start pulling in more packages to achieve a goal, certainly if you’re keeping performance in mind (with the likes of <a href="https://bundlephobia.com/">bundlephobia</a>), and said package may be doing more than you need.</p><p>I was in this situation recently — the project was already using <a href="https://www.npmjs.com/package/react-responsive">react-responsive</a>, a wrapper around the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia">matchMedia API</a> with some server-side options for fallbacks, which is only useful if there is a sensible default (uncommon) or if you are using hints to be able to pass through to the package. This doesn’t do enough as I didn’t have hints to work with and pass to the package, so more work was needed to enable CLS-less SSR.</p><p>Here’s the first pass solution I settled on for my use-case (subject to change, and mileage may vary):</p><pre>// index.tsx<br><br>import React, { useEffect, useState } from ‘react’;<br>import { useMediaQuery } from ‘react-responsive’;<br>import { BREAKPOINTS, BreakpointWidth } from ‘./breakpoints’;<br>import styles from ‘./index.css’;<br><br>interface SSRMediaQueryProps {<br>  maxWidth?: BreakpointWidth;<br>  minWidth?: BreakpointWidth;<br>}<br><br>interface DisplayValueProps extends SSRMediaQueryProps {<br>  breakpoint: BreakpointWidth;<br>}<br><br>const getDisplayValue = ({ breakpoint, maxWidth, minWidth }: DisplayValueProps) =&gt; {<br>  if (typeof maxWidth === ‘undefined’ &amp;&amp; typeof minWidth === ‘undefined’) {<br>    return undefined;<br>  } else if (typeof minWidth === ‘undefined’) {<br>    return breakpoint &gt;= maxWidth! ? ‘none’ : undefined;<br>  } else if (typeof maxWidth === ‘undefined’) {<br>    return breakpoint &lt; minWidth ? ‘none’ : undefined;<br>  }<br><br>  return breakpoint &lt; minWidth || breakpoint &gt;= maxWidth ? ‘none’ : undefined;<br>};<br><br>/**<br> * This component serves to allow viewport specific components be server-side<br> * rendered without causing a CLS.<br> */<br>export const SSRMediaQuery: React.FC&lt;SSRMediaQueryProps&gt; = ({ children, maxWidth, minWidth }) =&gt; {<br>  const nonInclusiveMaxWidth = maxWidth ? maxWidth - 1 : undefined;<br>  const [isClientSide, setIsClientSide] = useState(false);<br>  const isMatch = useMediaQuery({ maxWidth: nonInclusiveMaxWidth, minWidth });<br><br>  useEffect(() =&gt; {<br>    setIsClientSide(true);<br><br>    return () =&gt; {<br>      setIsClientSide(false);<br>    };<br>  }, []);<br><br>  if (!isClientSide) {<br>    const style = {<br>      &#39;--xs&#39;: getDisplayValue({ breakpoint: BREAKPOINTS.xs, maxWidth, minWidth }),<br>      &#39;--sm&#39;: getDisplayValue({ breakpoint: BREAKPOINTS.sm, maxWidth, minWidth }),<br>      &#39;--md&#39;: getDisplayValue({ breakpoint: BREAKPOINTS.md, maxWidth, minWidth }),<br>      &#39;--lg&#39;: getDisplayValue({ breakpoint: BREAKPOINTS.lg, maxWidth, minWidth }),<br>      &#39;--xl&#39;: getDisplayValue({ breakpoint: BREAKPOINTS.xl, maxWidth, minWidth }),<br>    } as React.CSSProperties;<br><br>    return (<br>      &lt;div className={styles.mediaQueryContainer} style={style}&gt;<br>        {children}<br>      &lt;/div&gt;<br>    );<br>  }<br><br>  if (isMatch) {<br>    return &lt;&gt;{children}&lt;/&gt;;<br>  }<br><br>  return null;<br>};</pre><p>Where the imported CSS file is roughly (I’m using SCSS mixins to avoid writing long-hand!):</p><pre>/* index.css */<br><br>@media (max-width: 543px) {<br>  .mediaQueryContainer {<br>    display: var(--xs, unset);<br>  }<br>}<br>@media (min-width: 544px) and (max-width: 767px) {<br>  .mediaQueryContainer {<br>    display: var(--sm, unset);<br>  }<br>}<br>@media (min-width: 768px) and (max-width: 991px) {<br>  .mediaQueryContainer {<br>    display: var(--md, unset);<br>  }<br>}<br>@media (min-width: 992px) and (max-width: 1199px) {<br>  .mediaQueryContainer {<br>    display: var(--lg, unset);<br>  }<br>}<br>@media (min-width: 1200px) {<br>  .mediaQueryContainer {<br>    display: var(--xl, unset);<br>  }<br>}</pre><p>And the usage is something like:</p><pre>&lt;Article&gt;<br>  &lt;SSRMediaQuery minWidth={BREAKPOINTS.sm}&gt;<br>    &lt;ArticleAuthorInformationDesktop /&gt;<br>  &lt;/SSRMediaQuery&gt;<br>  &lt;ArticleImage /&gt;<br>  &lt;ArticleHeading /&gt;<br>  &lt;ArticlePreviewText /&gt;<br>  &lt;SSRMediaQuery maxWidth={BREAKPOINTS.sm}&gt;<br>    &lt;ArticleAuthorInformationMobile /&gt;<br>  &lt;/SSRMediaQuery&gt;<br>&lt;/Article&gt;</pre><p>Walking through the implementation:</p><ol><li>We first assume that we are always in a server-side or pre-hydration world until an effect (which only runs client-side) tells us otherwise.</li><li>In this server-side world we always render the provided children, and furthermore, do so with them wrapped in a &lt;div&gt; that has appropriate styles associated with it to ensure it is only displayed to users for on the desired viewports. Here I was feeling fancy so made use of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS variables</a> to determine how the display value is set for the wrapper &lt;div&gt;, defaulting to unset if no value is passed.</li><li>Once we hit the client we can be safe in the knowledge that the CSS media queries on the wrapper &lt;div&gt; will kick in meaning that although we’ve taken a slight performance hit shipping both variations of component, we don’t suffer from and visual quirks or CLS.</li><li>Post hydration we’ll still be in a state where both variations exist — indeed both variations will render on first pass so care is needed to make sure your components don’t have “onload / onmount” like side effects which could “double fire”. (For further reading on why we do this double pass see <a href="https://github.com/facebook/react/issues/23381#issuecomment-1079494600">this GitHub issue</a>. There is a better alternative which is more involved that I’m not covering here – this uses suspense boundaries and manual pruning of the DOM to remove the unwanted tree prior to hydration)</li><li>Following the first render the effect will fire, this will update the state to switch to the client-side mode. This triggers a re-render where we either continue to render the children as there is a viewport match, or nothing otherwise.</li></ol><p>And just like that we’re free of CLS and can gracefully handle this kind of markup difference between different viewports 🎉</p><h3>Parting thoughts</h3><p>The unfortunate state is that there is no perfect answer here, just trade-offs:</p><ul><li>Get exactly what you want, but having to trust client hints which might not always be there… so maybe only sometimes getting what you want?</li><li>Have faster <a href="https://web.dev/ttfb/">time to first byte (TTFB)</a> and other similar page load metrics at the cost of having an annoying CLS vs almost certainly having CLS at the cost of page weight bloat slower that first impression.</li><li>Potential search engine optimisation (SEO) gotchas as a result of double rendering content (although it appears generally accepted that you shouldn’t get penalised for this kind of behaviour) coupled with the performance issues above for <a href="https://web.dev/vitals/">core web vitals</a> having a direct impact on SEO.</li><li>Care still need for side-effects in SSR double rendered components (unless you put in more work).</li></ul><p>Always evaluate what works for your use-case!</p><ul><li>If you’re in an SPA world where you only client-render, this article was not for you!</li><li>If you are in a SSR world but the component that can be rendered differently is <em>only </em>rendered upon customer interaction or some other lazy or deferred trigger, then you don’t need to worry — just use client-side techniques.</li><li>If you’re in a SSR world, maybe consider some of the techniques discussed.</li></ul><p>Hopefully this can save some research and thoughts for others in future. If you have any ideas, suggestions, or great ways (or packages) you use to solve this problem I’d love to hear it in the comments!</p><p>Like what I have to say? Consider a follow here or on <a href="https://twitter.com/CraigMorten">Twitter</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2d5ab0d05bfa" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automating a11y testing: Part 1 — Axe]]></title>
            <link>https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/ed3d215de126</guid>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Fri, 23 Jun 2023 07:43:30 GMT</pubDate>
            <atom:updated>2023-07-05T14:58:29.144Z</atom:updated>
            <content:encoded><![CDATA[<h3>Automating a11y testing: Part 1 — Axe</h3><p>Earlier this year I gave a talk at the John Lewis Partnership Tech Profession Conference 2023 on the topic of automating web accessibility (a11y) testing.</p><p>In this series I will cover the contents of this talk, kicking off with insights into the Axe suite of tools, followed by articles covering the wider a11y testing landscape: from visual regression testing for magnification and visual deficiencies, to emerging technology in the screen reader automation space.</p><h3>I am not the user</h3><p>Before going any futher I’m keen to express a disclaimer for this series: “I am not the user”!</p><p>I don’t have any training beyond “on the job” learnings, I am not a user of assistive technology in my day to day life outside of QA, and other than a mild astigmatism I consider myself able-bodied and neurotypical. Somewhat brings forth imposter syndrome when writing on the topic of a11y testing! Nevertheless, it’s something I’m passionate about.</p><p>Throughout this series I will be mostly focusing on the automation side of a11y testing, but it is also key to remember the importance of manual validation and exploratory testing. At the end of the day it is <em>real people</em> that are visiting and using your site, so it is incredibly important to ensure the quality of their experience, not just to tickbox that automation has passed. In fact, current tooling can <a href="https://karlgroves.com/web-accessibility-testing-what-can-be-tested-and-how/">only cover around 25% of WCAG requirements</a> (granted an old article, but still holds true!) and although later in this series we will explore some emerging technology to start pushing that coverage higher, there are still numerous aspects of the customer experience that can only be tested by a real person.</p><p>With that all covered off, let’s have a wander through the world of automated web accessibility testing!</p><h3>Current state of play</h3><h4>Axe</h4><p>It wouldn’t be an article about web accessibility testing if the Axe suite by Deque wasn’t mentioned. Putting simply, this tooling has somewhat of a monopoly at the moment on accessibility automation, and for good reason.</p><blockquote>“The axe family of tools help you check for digital accessibility” — <a href="https://www.deque.com/axe/">https://www.deque.com/axe/</a></blockquote><p>Outside of very specific, dedicated tools it has the best coverage out there, good APIs and interfaces, and although I think the technical documentation could be better (likely personal taste — it always ends up being there, just not structured how I expect), it is far from bad and is made up for by a wealth of community tutorials, articles, and repo examples.</p><p>Beyond their <a href="https://www.npmjs.com/package/axe-core">axe-core</a> package Deque also maintain a number of dedicated packages for the majority of popular frameworks in the web test automation space, namely:</p><ul><li><a href="https://www.npmjs.com/package/@axe-core/playwright">@axe-core/playwright</a></li><li><a href="https://www.npmjs.com/package/@axe-core/puppeteer">@axe-core/puppeteer</a></li><li><a href="https://www.npmjs.com/package/@axe-core/webdriverjs">@axe-core/webdriverjs</a> (think Selenium)</li><li><a href="https://www.npmjs.com/package/@axe-core/webdriverio">@axe-core/webdriverio</a></li><li><a href="https://www.npmjs.com/package/@axe-core/react">@axe-core/react</a></li></ul><p>To give you a flavour, here is a snippet of how you might use the Playwright integration to perform static analysis on the live Waitrose Groceries site to access the accessibility of the primary navigation menu, lovingly referred to as the “mega menu”:</p><pre>import { test, expect } from &#39;@playwright/test&#39;;<br>import AxeBuilder from &#39;@axe-core/playwright&#39;;<br><br>test(&#39;mega menu should not have a11y violations&#39;, async ({ page }) =&gt; {<br>  await page.goto(&#39;https://www.waitrose.com/ecom/shop/browse/groceries&#39;);<br><br>  await page.getByRole(&#39;button&#39;, { name: &#39;Groceries&#39; }).click();<br><br>  await page.locator(&#39;#megamenu&#39;).waitFor();<br><br>  const accessibilityScanResults = await new AxeBuilder({ page }).include(&#39;#megamenu&#39;).analyze();<br><br>  expect(accessibilityScanResults.violations).toEqual([]);<br>});</pre><p>Hopefully a very standard looking test — we navigate to the desired page, interact with the Groceries dropdown button, and wait for the mega menu to be visible. The Axe part is then remarkably straight-forward to get started with — we construct a new AxeBuilder, tell it to inspect the mega menu element tree, and run the analysis. Across the other framework packages the API usage is very similar.</p><h4>cypress-axe</h4><p>Now the eagle eyed of you will have noticed there was a popular framework missing from the list of Deque owned packages for Axe — Cypress!</p><p>Don’t worry, there is a really good community package to support Cypress as well in the form of the <a href="https://www.npmjs.com/package/cypress-axe">cypress-axe</a> package.</p><p>The interface is a little different to how you use the other Axe packages, but personally I almost prefer the style which works quite naturally with the Cypress API style. Let’s see how we might test the accessibility of a John Lewis Search Product Listing Page (PLP):</p><pre>describe(&#39;PLP Search A11y Violations&#39;, () =&gt; {<br>  beforeEach(() =&gt; {<br>    cy.visit(&#39;https://www.johnlewis.com/search?search-term=vans&#39;);<br><br>    cy.injectAxe();<br><br>    cy.configureAxe(AXE_CONFIG_OPTIONS);<br>  });<br><br>  it(&#39;should not have a11y violations on load&#39;, () =&gt; {<br>    cy.checkA11y(SELECTOR, AXE_RUN_OPTIONS);<br>  });<br>});</pre><p>As is often the case with Cypress, the feedback loop from <a href="https://www.npmjs.com/package/cypress-axe">cypress-axe</a> is also up there with clear logging explaining the issues when you are using the Cypress UI.</p><p>For example, in this failed test we can see the error description, some guidance, a URL for more information, and if we were to drill into the Nodes array it would give us pointers to the exact elements that are in violation of WCAG.</p><figure><img alt="Cypress UI with a failed test for “Has no a11y violations after button click”. The test lists the error “A11Y ERROR! Heading-order on 2 Nodes” with message “1 accessibility violation was detected: expected 1 to equal 0”. This error has been pinned and in the Chrome developer tools the Console tab is open listing further details on the error, including the error id, impact, tags, description, help description and url, and the relevant DOM nodes." src="https://cdn-images-1.medium.com/max/1024/1*O6Btbgz7gIO9XSm-Yz8Yyw.png" /><figcaption><a href="https://www.npmjs.com/package/cypress-axe">cypress-axe</a> logs in the Cypress UI</figcaption></figure><h4>Pa11y</h4><p>So what if you don’t currently use one of the aforementioned frameworks for your site?</p><p>In cases where you don’t have a setup ready to plug and play with one of the previous packages, and perhaps you’ve also not got the knowledge or resource to spend building up a suite using one of those frameworks (though these days the developer experience for setting up is pretty good!), then I would recommend taking a peek at <a href="https://github.com/pa11y/pa11y">Pa11y</a>.</p><blockquote>“Pa11y is your automated accessibility testing pal. It runs accessibility tests on your pages via the command line or Node.js, so you can automate your testing process.” — <a href="https://pa11y.org/">https://pa11y.org/</a></blockquote><p>The nice thing about Pa11y is you can run it straight from the command line making it natural to use for scripting, or for simple smoke checks in CI. It also ships a Node package so you can write a quick test in JavaScript or Typescript. Because it is a “wrapper” you can also benefit from it’s dual coverage utilising both Axe and <a href="https://squizlabs.github.io/HTML_CodeSniffer/">HTML Code Sniffer (htmlcs)</a>, another static analysis tool. The documentation for Pa11y is also <em>really</em> good.</p><p>Let’s take a look at an example setup to test the page accessibility with the mega menu open:</p><pre>import pa11y from &#39;pa11y&#39;;<br><br>const BASE_PA11Y_CONFIGURATION = {<br>  runners: [&#39;axe&#39;, &#39;htmlcs&#39;],<br>  standard: &#39;WCAG2AAA&#39;,<br>};<br><br>async function runPa11yTest() {<br>  try {<br>    const results = await pa11y(&#39;https://www.waitrose.com/ecom/shop/browse/groceries&#39;, {<br>      actions: [&#39;click element #drop-down-nav&#39;, &#39;wait for element #megamenu to be visible&#39;],<br>      ...BASE_PA11Y_CONFIGURATION,<br>    });<br><br>    console.log(results);<br>  } catch (error) {<br>    console.error(error);<br>  }<br>}<br><br>runPa11yTest();</pre><p>Pa11y’s actions API for perfoming customer interactions is a little restricted, but it does has you covered for basic behaviours like clicking, typing, and waiting for something to be visible. Because it uses English based instructions, it has a similar feel to using BDD testing libraries (e.g. think Gherkin syntax for Cucumber) which lowers the barrier to entry for developers or even non-developers to easily tweak or extend the scope of tests — caveated of course if English is not your first language!</p><p>Unfortunately the style of this actions API does mean the tool doesn’t sit well within other frameworks — if you’re trying to use Playwright or Cypress to first act on the page for setup it will just get ignored. What Pa11y does under the hood is use Puppeteer to spin up it’s own Chrome tab to instrument and act out the instructions and a11y testing, so anything set up outside of Pa11y’s actions array will effectively be ignored.</p><p>But if you’re performing integration testing through the likes of Jest, Vitest, Mocha, etc., i.e. a testing framework that doesn’t support it’s own browser instrumentation, this can be a nice fit to extend a suite of tests to include a11y coverage.</p><p>In summary:</p><ul><li>Conveniently wraps both Axe and htmlcs</li><li>Really good documentation</li><li>Simple to use action, though somewhat restricted</li><li>Doesn’t play so well with others</li></ul><h4>Axe browser extension</h4><p>Moving out of what some folks might strictly class as test automation, it’s worth discussing some of the other tooling in the Axe family.</p><figure><img alt="John Lewis website open on the   coffee table product listing page in Chrome. The developer tools pane is open on the axe DevTools tab showing the overview of an Axe scan. The report lists 4 accessibility issues in a list of expandable sections, with one for “Images must have alternative text” expanded to show details of the issue. A highlight button is toggled visually highlighting the image on the page that is missing an alt tag." src="https://cdn-images-1.medium.com/max/1024/1*C52eAZ_FiBz6XjNJWwPnMQ.png" /><figcaption><a href="https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd">Axe browser extension</a> running in Chrome Developer Tools</figcaption></figure><p>To complement the Axe packages, Deque also have a really nice <a href="https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd">Axe browser extension</a>. Some features are premium, but the “scan all” feature meets most, if not all, needs for supplementing exploratory testing with a degree of automation, taking the heavy lifting away from manually inspecting HTML, colours, etc. to try and work out if components are compliant.</p><h4>Axe Linter</h4><p>There is also a VSCode plugin for an <a href="https://marketplace.visualstudio.com/items?itemName=deque-systems.vscode-axe-linter">Axe Linter</a> which can be a useful tool in proactively writing accessible code write at the early development stage — eliminating the slower feedback loop of only finding violations at your integration test stage in CI say.</p><figure><img alt="A VSCode window with code for a React based Image component. The cursor is hovered over the img element JSX which doesn’t have an alt prop. The component has a red error underline and a hover tooltip which reads: “Axe Linter (image-alt): Ensures img elements have alternative text or a role of none or presentation (dequeuniversity/image-alt)”." src="https://cdn-images-1.medium.com/max/1024/1*xQxoLBGAhMOPwTno__L6zA.png" /></figure><h4>Lighthouse</h4><p>Lastly something that hopefully might be familiar to most readers!</p><p>If you’ve ever used the Accessibility reporting feature of Google Lighthouse, it uses Axe under the hood to drive the tests and provide the nicely displayed violation information.</p><p>This is not just the case for the Chrome DevTools instance of Lighthouse — the <a href="https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk">browser extension</a>, <a href="https://github.com/GoogleChrome/lighthouse">CLI, and Node package</a> all make use of Axe for the accessibility score and reporting features.</p><figure><img alt="Waitrose Groceries favourites page open in Chrome using iOS mobile simulation. The DevTools panel is open on the Lighthouse tab displaying an Accessibility score for the page. WCAG violations are listed with descriptions of the issue, and references to the impacted elements with screenshots highlighting the elements." src="https://cdn-images-1.medium.com/max/1024/1*9PL9iukKsLT3JDHUMzRuOg.png" /><figcaption>Google Chrome Lighthouse DevTools</figcaption></figure><p>There are also some nice packages that wrap Lighthouse for easy use in test automation — for example, the <a href="https://www.npmjs.com/package/cypress-audit">cypress-audit</a> package for Cypress worthy of a mention for it’s easy to use <a href="https://github.com/mfrachet/cypress-audit/blob/HEAD/docs/lighthouse.md">cy.lighthouse()</a> command which can also be used for performance budget tests (it also has a <a href="https://github.com/mfrachet/cypress-audit/blob/HEAD/docs/pa11y.md">cy.pa11y()</a> command so you can kick off Pa11y tests if you prefer that interface instead).</p><h3>Axe lessons</h3><p>Having realed off and recommended a number of Axe tools, it’s worth pointing out some of the gotcha’s that I’ve experienced with them over the years.</p><h4>React Axe issues</h4><p>A big lesson for me has been around <a href="https://www.npmjs.com/package/@axe-core/react">@axe-core/react</a>. Initially I was very much in favour in the idea of having a tool that can instrument React and report issues to the console while running apps locally in development.</p><p>However… for any application of any size this package can massive tank performance which can make local development painful and slow — especially if you are running a hot module reloading (HMR) setup where every small tweak triggers a hot reload and a potential new Axe scan of the page. If you are using it currently and ever wonder why animations are super laggy and page updates look jarring, then React Axe is a real possibility as to why. Obviously this is my experience working on large applications, mileage may vary!</p><p>My personal recommendation is to scrap React Axe and instead reach for the Axe browser extension instead — you don’t get “live” reports to the console, but the flow is fairly natural and is a definite improvement for developer experience. The UI and level of detail you get on the extension is also far superior to the red text you get in the console scattered amongst everything else that is getting logged.</p><h4>Infallible?</h4><p>It’s also worth calling out that Axe isn’t perfect — it can occasionally have quite annoying false positives.</p><p>The most common case I’ve found is in it’s reporting of colour contrast, specifically when doing anything complex with either nested elements or using pseudo-elements.</p><p>For example, imagine you have a setup (admittedly contrived, but funnily enough I have experienced this in a side menu implementation!) where there is an element with a green background and then a number of nested transparent that cover the parent element. Above these is then say an absolutely positioned element, or CSS “Z translated” element, or perhaps an element from an entirely different part of the DOM positioned also above the green element and it’s covering children. Say this too has some nested children — the top layer of which has some white text.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SDRe51FHo0fcVTtsDKbcUQ.png" /></figure><p>The white text is above the green background, so this is WCAG AA compliant but the element containing the text is sufficiently removed from the background element that Axe fails to recognise which elements (and colours) are involved in the contrast comparison and fails with a violation.</p><p>The trick here is to keep it simple — if you need to ensure contrast just apply the appropriate background (or equivalent) to the element with the text. Yes, this is somewhat fixing a non-issue due to tooling limitations, but it is slightly less fragile to changes in your page markup implementation — in future someone might refactor to reduce the complexity of this component, and having the element satisfy the necessarily colour contrast requirements itself rather than relying on some other element will save future rework getting things compliant again in such a refactor. As the principle says, “things that move together should sit together”.</p><p>There is also another lesson here — if you’re seeing false positives reported from Axe you’re probably doing something off in your markup. There are few scenarios that warrant such a complex element layout so Axe failing is probably a good hint that you’re in need of simplifying your HTML. Even if you are able to successfully satisfy the Axe requirement, it is very likely with such a layout that you will have potential issues with other accessibility requirements —what about users who make use of 2x zoom, or assistive technologies like magnifiers and screen readers? There is a real risk with something like above behaving poorly for some of these tools, e.g. screen readers are known for not handling non-semantic deeply nested markup very well.</p><h4>Friction in WIP projects</h4><p>Unless you’re in the rare position of starting from scratch with a greenfield project, the chances are you may well be pulling something like Axe into an existing project for a site that isn’t all that accessible at the moment, as a means to start improving the situation.</p><p>The thing to be aware of here is Axe’s limitations on ignoring violations, a capability that is often needed as a practical measure when you are looking to progressively improve a site’s accessibility which has a number of existing violations.</p><p>Of course ideally we don’t want to be ignoring anything, but a nice flow when working on such a codebase is to always create a ticket, and then add ignore rules named after or commented with the ticket reference and start ticking these off.</p><p>The frustrating thing with Axe ignores is that you can’t ignore a specific rule for a specific element. Unfortunately you can either ignore all rules for an element, or you can ignore all cases of a rule. The consequence is that often you will be over-ignoring elements or rules until you have cleared the decks of most or all violations, meaning until then you’re not really getting the protection from regression that you might hope for — folks adding new code may well introduce further violations of a similar type and Axe starts ignoring these as well!</p><p>What I recommend here is set up your tests from the start with sensible per test configuration and avoid having a single global configuration (though for DRY, it might be some common config is centralised). This way you can narrow the exclusions just for specific tests, e.g. say the contrast is only inaccessible when you toggle a button, you don’t want to ignore that button or the contrast rule for all other tests and scenarios in your suite!</p><p>Another technique that I’ve seen employed to reasonable effect is to introduce attributes to elements that you want to ignore, for example data-axe-ignore=&quot;true&quot;, and use this as a way to target elements that you need to temporarily ignore (through a selector such as [data-axe-ignore=&quot;true&quot;]) in order to get CI up and running with Axe tests. Once you fix the violation, just remember to remove the data attribute! This does have the downside of polluting production markup unless you have steps to purge the attributes, so there are trade-offs here.</p><h3>Closing notes</h3><p>In this article I’ve covered off the majority of the Axe suite, if you want to find out more or explore some of Deque’s other offerings check out their site at <a href="https://www.deque.com/axe/">https://www.deque.com/axe/</a>.</p><p>In part 2 of this series I will cover a number of non-Axe tools and techniques for a11y test automation. See you there! 👋</p><p><em>Hi, my name is </em><a href="https://twitter.com/CraigMorten"><em>Craig Morten</em></a><em>. I am a senior product engineer at the John Lewis Partnership. When I’m not hunched over my laptop I can be found drinking excessive amounts of tea or running around in circles at my local athletics track.</em></p><p>The John Lewis Partnership are hiring across a range of roles. If you love web development and are excited by accessibility, performance, SEO, and all the other awesome tech in web, we would love to hear from you! See our open positions <a href="https://www.jlpjobs.com/engineering-jobs/">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ed3d215de126" width="1" height="1" alt=""><hr><p><a href="https://medium.com/john-lewis-software-engineering/automating-a11y-testing-part-1-axe-ed3d215de126">Automating a11y testing: Part 1 — Axe</a> was originally published in <a href="https://medium.com/john-lewis-software-engineering">John Lewis Partnership Software Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reducing a cryptic memory leak]]></title>
            <link>https://medium.com/@craigmorten/reducing-a-cryptic-memory-leak-in-production-e36384208ea5?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/e36384208ea5</guid>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[expressjs]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Sat, 18 Mar 2023 15:59:21 GMT</pubDate>
            <atom:updated>2024-01-27T14:53:27.836Z</atom:updated>
            <content:encoded><![CDATA[<h3>“Reducing” a cryptic memory leak in Production</h3><p>I recently came across an interesting memory leak scenario which caught me off-guard, let’s see if you can spot the issue before the reveal at the end?</p><p>Below is some simplified replica code:</p><pre>const express = require(&quot;express&quot;);<br>const { getSponsored, getRecommendations, getProducts } = require(&quot;./fetchers&quot;);<br><br>const app = express();<br><br>const baseResponse = {<br>  filters: [],<br>  products: [],<br>  subCategories: [],<br>};<br><br>function mergeResponses(responses) {<br>  return responses.reduce((finalResponse, response) =&gt; {<br>    finalResponse.filters.push(...response.filters);<br>    finalResponse.products.push(...response.products);<br>    finalResponse.subCategories.push(...response.subCategories);<br><br>    return finalResponse;<br>  }, baseResponse);<br>}<br><br>app.use(&quot;/products&quot;, async (req, res) =&gt; {<br>  const responses = await Promise.all([<br>    getSponsored(),<br>    getRecommendations(req),<br>    getProducts(),<br>  ]);<br><br>  res.send(mergeResponses(responses));<br>});<br><br>app.listen(3000);</pre><p>The functionality is relatively straight-forward — we have a /products endpoint which is getting some sponsored, recommended, and normal products, merging parts of the results, and returning this aggregation as the response.</p><p>Now the use of Array.prototype.reduce() for the response merge is curious — we’re concatenating arrays so a simple for loop or .forEach() feels more appropriate, but hey for small responses at reasonably low RPS this is micro-optimisation, nothing to get too hung up on!</p><figure><img alt="MacBook with VSCode open on an Express JS file." src="https://cdn-images-1.medium.com/max/1024/0*lm55U33s-VhmQDyN" /><figcaption>Photo by <a href="https://unsplash.com/pt-br/@clemhlrdt?utm_source=medium&amp;utm_medium=referral">Clément Hélardot</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>But yet running something similar in a production like environment it was apparent that something was off… monitoring showed that old heap size was growing linearly with the number of requests — we had a memory leak!</p><blockquote>The memory heap is divided into two major spaces: the New space where new objects are allocated, and the Old space where older objects are moved to after surviving for some time having not been garbage collected.</blockquote><h3>Debugging a memory leak</h3><p>A straight-forward way to start an investigation into a Node memory leak when you can run the project locally (debugging in production is a different matter!) is to use the <a href="https://nodejs.org/en/docs/guides/debugging-getting-started">Node inspector</a>, this can be invoked easily through a flag when starting your Node application:</p><pre>$ node --inspect index.js<br><br>Debugger listening on ws://127.0.0.1:9229/2a7f05cd-96c8-4f6d-8c5f-adea987ab63b<br>For help, see: https://nodejs.org/en/docs/inspector</pre><p>This will prompt the Node process to listen for a debugging client which, once connected, allows you to instrument your application with breakpoints as well as resource metrics and analysis for CPU and memory.</p><p>There a few options for debuggers — major IDEs like Visual Studio, Visual Studio Code, and JetBrains Webstorm all support the protocol making for easy debugging from within your editor. Here I’ll demonstrate an alternative, using Chrome DevTools.</p><p>Once your Node process is running with the inspector, open up Chrome and navigate to chrome://inspect in the URL address bar.</p><figure><img alt="Chrome browser open on the chrome://inspect page, listing a target for Node v14.20.1 for index.js and an option to inspect." src="https://cdn-images-1.medium.com/max/1024/1*owDNriAsl4_XKP0ZZ4dB6w.png" /><figcaption>Chrome Inspect Page</figcaption></figure><p>Here under the “Remote Target” section we can click on the “inspect” link to starting interrogating our index.js Node process.</p><figure><img alt="Chrome DevTools window open on the Memory tab showing options for selecting a profiling type." src="https://cdn-images-1.medium.com/max/1024/1*Q4IQ0vsyJ__OEIL-hMmLjA.png" /><figcaption>Chrome DevTools Memory Tab</figcaption></figure><p>Clicking this link pops open a new Chrome DevTools tab which is very similar to what you would get while debugging a page in the browser.</p><p>On the Memory tab we can click the “Take snapshot” button while the “Heap snapshot” option is selected to record a snapshot in time of the memory heap. This will pop up on the left sidebar and generally will automatically open up.</p><figure><img alt="Chrome DevTools heap snapshot listing rows of Constructors." src="https://cdn-images-1.medium.com/max/1024/1*rlaZzAoqZbI0ooUEAgznmA.png" /><figcaption>Chrome DevTools Memory Heap Snapshot</figcaption></figure><p>From this snapshot we can see all of the “constructors” (think primitives or objects) with columns for the distance from the window object, the size of each object in bytes, and the retained size of the object in bytes (the size of the object and the graph it retains).</p><p>This doesn’t yet give us enough information to easily assess where a memory leak might be, but can be interesting to peruse to understand the shape of your application’s memory.</p><p>Now in our case the memory leak appeared to be correlated to the amount of traffic the server was receiving, so to simulate that we can use tools like autocannon to very easily generate some base level load on the server.</p><p>Here was run one of three:</p><pre>$ npx autocannon -c 100 http://localhost:3000/products <br>  <br>Running 10s test @ http://localhost:3000/products<br>100 connections<br><br><br>┌─────────┬────────┬─────────┬─────────┬─────────┬───────────┬───────────┬─────────┐<br>│ Stat    │ 2.5%   │ 50%     │ 97.5%   │ 99%     │ Avg       │ Stdev     │ Max     │<br>├─────────┼────────┼─────────┼─────────┼─────────┼───────────┼───────────┼─────────┤<br>│ Latency │ 158 ms │ 1232 ms │ 2860 ms │ 3440 ms │ 1296.6 ms │ 786.21 ms │ 5350 ms │<br>└─────────┴────────┴─────────┴─────────┴─────────┴───────────┴───────────┴─────────┘<br>┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐<br>│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │<br>├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Req/Sec   │ 27      │ 27      │ 46      │ 147     │ 61.8    │ 35.14   │ 27      │<br>├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤<br>│ Bytes/Sec │ 34.4 MB │ 34.4 MB │ 60.6 MB │ 80.7 MB │ 60.7 MB │ 13.3 MB │ 34.4 MB │<br>└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘<br><br>Req/Bytes counts sampled once per second.<br># of samples: 10<br><br>718 requests in 10.09s, 607 MB read</pre><p>… and run three of three:</p><pre>$ npx autocannon -c 100 http://localhost:3000/products<br><br>Running 10s test @ http://localhost:3000/products<br>100 connections<br><br><br>┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐<br>│ Stat    │ 2.5%    │ 50%     │ 97.5%   │ 99%     │ Avg        │ Stdev      │ Max     │<br>├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤<br>│ Latency │ 1554 ms │ 5400 ms │ 9049 ms │ 9189 ms │ 5389.29 ms │ 2438.84 ms │ 9314 ms │<br>└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘<br>┌───────────┬─────┬──────┬─────────┬────────┬─────────┬─────────┬─────────┐<br>│ Stat      │ 1%  │ 2.5% │ 50%     │ 97.5%  │ Avg     │ Stdev   │ Min     │<br>├───────────┼─────┼──────┼─────────┼────────┼─────────┼─────────┼─────────┤<br>│ Req/Sec   │ 0   │ 0    │ 6       │ 36     │ 10.6    │ 11.62   │ 6       │<br>├───────────┼─────┼──────┼─────────┼────────┼─────────┼─────────┼─────────┤<br>│ Bytes/Sec │ 0 B │ 0 B  │ 22.4 MB │ 128 MB │ 38.1 MB │ 41.5 MB │ 22.4 MB │<br>└───────────┴─────┴──────┴─────────┴────────┴─────────┴─────────┴─────────┘<br><br>Req/Bytes counts sampled once per second.<br># of samples: 10<br><br>232 requests in 10.09s, 381 MB read<br>26 errors (26 timeouts)</pre><p>We can see here that something is very definitely wrong — after only 3 batches of 10 second load periods serving 100 connections we’ve gone from an average latency of 1296.6 ms to 5389.29 ms, a degradation of over 4 seconds!</p><p>Let’s take another heap snapshot using the “record” button at the top left of the Chrome DevTools Memory tab.</p><figure><img alt="Chrome DevTools window open on the Memory tab with the record button highlighted on the top left of the Memory tab’s control panel. A second heap snapshot is open." src="https://cdn-images-1.medium.com/max/1024/1*qMuSPPMZ5Fog859UAG2zLg.png" /><figcaption>Chrome DevTools Memory Heap Snapshot 2</figcaption></figure><p>We can see that there certainly appears to be a lot more memory in play — the total snapshot size (listed under the snapshot name on the left sidebar) has increased from 5.6 MB to 24.7 MB and there are some large numbers listed for shallow and retained size.</p><p>However hunting blind in any one snapshot doesn’t get you very far typically, where the DevTools comes into it’s own is with the comparison options.</p><p>The last option in the Memory Tab’s control panel is a dropdown which will state “All objects” by default. Clicking on this dropdown gives you a number of options to compare between snapshots.</p><figure><img alt="Chrome DevTools window open on a heap snapshot, with the comparison dropdown open in the Memory Tab control panel. Listing options: All objects, Objects allocated before Snapshot 1, Objects allocated between Snapshot 1 and Snapshot 2." src="https://cdn-images-1.medium.com/max/1024/1*DScmAHekb1HVHb1QQOKgOA.png" /><figcaption>Chrome DevTools Memory Heap Comparison Dropdown</figcaption></figure><p>Here we select the “Objects allocated between Snapshot 1 and Snapshot 2” option to show us the delta between the two snapshots.</p><figure><img alt="Chrome DevTools window showing the difference between Snapshot 1 and Snapshot 2" src="https://cdn-images-1.medium.com/max/1024/1*TScH5MiYe_PiVC0HGSVGWg.png" /><figcaption>Chrome DevTools Memory Heap Comparison View</figcaption></figure><p>This immediately reduces the number of constructors in play when compared with the long lists for the individual snapshots — anything that hasn’t changed between the snapshots hasn’t been listed, removing a lot of the noise from the view.</p><p>Now comes the interesting part — finding the source of the leak! We start by sorting the list by retained size (typically the default) and navigating down the list of constructors one by one. Here we expand the (string) options first and take a look:</p><figure><img alt="Chrome DevTools window with the strings memory expanded." src="https://cdn-images-1.medium.com/max/1024/1*VQf-_ViFxHXOpUYB6ywypw.png" /><figcaption>Chrome DevTools Memory Heap strings</figcaption></figure><p>There’s nothing of interest here — we can tell from the small shallow and retained sizes of each of the strings listed that the issue doesn’t lie here. Although the combined size of each string does make for the largest sized collection, the size associated with each string is very small.</p><p>Intuitively we’re also not expecting a string to be the primary cause of our problem — nowhere in our logic are we growing a string (perhaps if we were doing some sort of string concatination, but we’re not). It’s apparent that a lot of these strings are likely long-lived constants, we likely took our first snapshot too early — if we were to take a third snapshot they would like be removed from the comparison view now the server has had some requests.</p><p>Moving onto the second constructor we can see something a little more interesting:</p><figure><img alt="Chrome DevTools window with the arrays memory expanded." src="https://cdn-images-1.medium.com/max/1024/1*JBH2UYY6uzz3_ZwuVoo5xw.png" /><figcaption>Chrome DevTools Memory Heap Comparison arrays</figcaption></figure><p>Here the shallow and retained sizes for the top three children are particularly large considering this is a delta!</p><p>Clicking on the first (object elements)[] row we can populate the “Retainers” bottom panel with detailed information on the object in question. This immediately points to a suspect:</p><figure><img alt="Chrome DevTools window with the arrays memory expanded and the first object selected. The Retainers bottom panel is open showing tree graph view of object relationships and their memory allocation." src="https://cdn-images-1.medium.com/max/1024/1*jfOpBJQ84Kgha91NX9V8qg.png" /><figcaption>Chrome DevTools Memory Heap Retainers View</figcaption></figure><p>In the bottom panel we can see that the massive retained size has been associated with the elements in a products array that exists on a baseResponse object used by the Express middleware stack! If we click on the second and third child we can see that these are for subCategories and filters respectively.</p><p>Somehow these arrays on our baseResponse object are growing in size after requests. This must mean that they are being mutated with more and more values! In fact if we expand the (object elements)[] children we can see the exact values that are populating our arrays.</p><p>So our leak is something causing the baseResponse arrays to grow on a per request basis… lets go have another look.</p><blockquote>Want to learn more about debugging memory with Node? Checkout the <a href="https://nodejs.org/en/docs/guides/diagnostics/memory/using-heap-snapshot">Node Memory Diagnostics tutorial page</a>.</blockquote><h3>When does a reduce grow?</h3><p>Let’s take another look at our mergeResponses() method:</p><pre>const baseResponse = {<br>  filters: [],<br>  products: [],<br>  subCategories: [],<br>};<br><br>function mergeResponses(responses) {<br>  return responses.reduce((finalResponse, response) =&gt; {<br>    finalResponse.filters.push(...response.filters);<br>    finalResponse.products.push(...response.products);<br>    finalResponse.subCategories.push(...response.subCategories);<br><br>    return finalResponse;<br>  }, baseResponse);<br>}</pre><p>We’re reducing over the array of responses, accumulating the associated filters, products, and subCategories and return the result. And in doing so somehow the baseResponse is being mutated?</p><p>Well exactly that — TIL!</p><p>Turns out that although Array.prototype.reduce() does not mutate the underlying array it does mutate the initialValue if it is provided. In many cases where you might provide a value directly like a number or an empty object this is fine, but if you create a bootstrap object like we have here then it will get mutated by each call, and re-used every time!</p><p>This isn’t mentioned anywhere on <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#initialvalue">MDN</a> or similar from what I can find, so is a bit of a gotcha — though I suspect the number of times this pattern is used is likely (hopefully) small.</p><p>So how do we fix this issue?</p><p>Well one option is to move the baseResponse into the mergeResponses() method so that it is no longer in top level scope and can be garbage collected once the request has been responded to. I.e.</p><pre>function mergeResponses(responses) {<br>  const baseResponse = {<br>    filters: [],<br>    products: [],<br>    subCategories: [],<br>  };<br><br>  return responses.reduce((finalResponse, response) =&gt; {<br>    finalResponse.filters.push(...response.filters);<br>    finalResponse.products.push(...response.products);<br>    finalResponse.subCategories.push(...response.subCategories);<br><br>    return finalResponse;<br>  }, baseResponse);<br>}</pre><p>Or as we opted for, move away from a reduce when we’re fundamentally not changing object shape:</p><pre>function mergeResponses(responses) {<br>  const baseResponse = {<br>    filters: [],<br>    products: [],<br>    subCategories: [],<br>  };<br><br>  responses.forEach((response) =&gt; {<br>    baseResponse.filters.push(...response.filters);<br>    baseResponse.products.push(...response.products);<br>    baseResponse.subCategories.push(...response.subCategories);<br>  });<br><br>  return baseResponse;<br>}</pre><p>So did you spot the memory leak before the big reveal?</p><p>Have you got the same code in your apps? 😱</p><p>Have you faced a memory leak recently?</p><p>Let me know in the comments along with your thoughts, comments, and queries! Till next time folks 🚀</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e36384208ea5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A11y Unlocked: Screen-Reader Automation Tests]]></title>
            <link>https://medium.com/@craigmorten/a11y-unlocked-screen-reader-automation-tests-788b5c6bac8d?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/788b5c6bac8d</guid>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[accessibility]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Sun, 16 Oct 2022 17:31:50 GMT</pubDate>
            <atom:updated>2024-01-27T14:53:51.390Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>In the past year since my last post <a href="https://dev.to/craigmorten/a11y-testing-automating-screenreaders-1a3n">A11y Testing: Automating ScreenReaders</a> on the topic of automating screen readers, we’re now in a better place with progress made in standards and tooling.</p><p>It is exciting to see the <a href="https://aria-at.w3.org/">W3C ARIA-AT Community Group</a> formed with a mission to improve screen reader interoperability by building out a suite of specifications, test standards, and test automation to mature the screen reader ecosystem towards first-class tooling.</p><blockquote>“Enabling AT interoperability is a large, ongoing endeavor that requires industry-wide collaboration and support. The W3C ARIA-AT Community Group is focusing on a stable and mature test suite for five screen readers by the end of 2023. In the future, we plan to test additional screen readers and other kinds of assistive technologies with a broader set of web design patterns and test material.”</blockquote><blockquote>Source: <a href="https://aria-at.w3.org/">https://aria-at.w3.org/</a></blockquote><p>While the standards community slowly work towards their long term goal, I am pleased to share some screen reader automation tooling which you can use <strong>today</strong>! 🚀</p><h3>Guidepup for playwright</h3><p>In this tutorial we’re going to make use of the package <a href="https://www.npmjs.com/package/@guidepup/playwright">@guidepup/playwright</a> to write e2e tests for testing the accessibility of pages for people who use the VoiceOver screen reader for MacOS.</p><figure><img alt="Logo of guidedog puppy with text: VoiceOver Automation With Guidepup" src="https://cdn-images-1.medium.com/max/952/1*xLtNSc_vTYtib_8n6kkcQQ.png" /></figure><p>The package provides a reliable set of APIs to automate your screen reader a11y workflows when using Playwright. It does this by providing both Playwright configuration as well as a custom Playwright test instance that exposes a voiceOver object for controlling VoiceOver with.</p><h3>Getting setup</h3><p>Let’s create a new project:</p><pre>npm init</pre><p>Install the @guidepup/playwright package as a dependency:</p><pre>npm install @guidepup/playwright</pre><p>We’ll also need Playwright as the test runner for our browser:</p><pre>npm install @playwright/test</pre><p>Finally we’ll want to test against Safari using Playwright, so let’s use their tool get grab the browser:</p><pre>npx playwright install webkit</pre><p>We now have all the dependencies we need! 🚀</p><h3>A touch of config</h3><p>Create a new playwright.config.js file:</p><pre>import { voConfig } from &quot;@guidepup/playwright&quot;;<br><br>const config: PlaywrightTestConfig = {<br>  ...voConfig,<br><br>  // Your custom config ...<br>};<br><br>export default config;</pre><p>Here we’re pulling in the minimum recommended Playwright config for using Guidepup — you can <a href="https://github.com/guidepup/guidepup-playwright/blob/main/src/voConfig.ts">check it out in the source code</a>. This sets the number of workers to 1 and sets Playwright to use &quot;headed&quot; mode because when we&#39;re using VoiceOver we can only control one browser at a time (and ideally it&#39;s visible!).</p><p>We should now be completely set to get going with our first test! 🎉</p><h3>Writing your first screen-reader test</h3><p>Create a new file voiceover.spec.js and copy in the following contents:</p><pre>// We use a special test instance from the Guidepup package<br>// that gives us access to VoiceOver!<br>import { voTest as test } from &quot;@guidepup/playwright&quot;;<br>import { expect } from &quot;@playwright/test&quot;;<br><br>// The test setup is exactly the same as normal for<br>// Playwright, expect we now get a `voiceOver` object as well<br>// as the `page` object!<br>test.describe(&quot;Playwright VoiceOver&quot;, () =&gt; {<br>  test(&quot;I can navigate the Guidepup Github page&quot;, async ({<br>    page,<br>    voiceOver,<br>  }) =&gt; {<br>    // Let&#39;s navigate to Guidepup GitHub page and wait for<br>    // page to be ready, nothing special here!<br>    await page.goto(&quot;https://github.com/guidepup/guidepup&quot;, {<br>      waitUntil: &quot;domcontentloaded&quot;,<br>    });<br>    await expect(<br>      page.locator(&#39;header[role=&quot;banner&quot;]&#39;)<br>    ).toBeVisible();<br><br>    // This is where things start to get awesome! Let&#39;s tell<br>    // VoiceOver to interact with the main page content, just <br>    // the same as you would when use VoiceOver normally.<br>    await voiceOver.interact();<br><br>    // Let&#39;s do something a lil more exciting - move across<br>    // the page&#39;s headings until we reach the main Guidepup<br>    // repo title in the README using VoiceOver!<br>    while ((await voiceOver.itemText()) !== &quot;Guidepup heading level 1&quot;) {<br>      await voiceOver.perform(<br>        voiceOver.keyboard.commands.findNextHeading<br>      );<br>    }<br>});</pre><p>Check out the comments in the code for what will happen in each part — pretty awesome huh? 🙌</p><h3>Last checks before we launch</h3><p>In order to automate screen-readers a few OS level settings need to be sorted first!</p><p>You can head over to the <a href="https://www.guidepup.dev/docs/guides/environment">Guidepup environment setup docs</a> for more details, but I recommend trying out the <a href="https://github.com/guidepup/setup">@guidepup/setup</a> package which is designed to get you setup no faff!</p><pre>npx @guidepup/setup</pre><p>You’re now ready to rock! 🤘</p><h3>Running your first screen-reader test</h3><p>No special commands needed here, just the same as with any other Playwright test! Fire off the command then hold tight — we don’t want to interact with the machine while the test is running as it might move VoiceOver’s focus!</p><pre>npx playwright test</pre><figure><img alt="Gif of Playwright controlled Safari browser being driven with VoiceOver. Announcements read: “heading level 2, Latest commit”, “heading level 2, Git stats”, “You are currently on a heading level 2.”, “heading level 2, Files”, “You are currently on a heading level 2.”, “heading level 2, link, README.md”, “heading level 1, Guidepup”" src="https://cdn-images-1.medium.com/max/880/1*z63IEm0qHHFP26_uhI1rNg.gif" /><figcaption>A11y test for a GitHub page on Safari using Guidepup to drive VoiceOver automation.</figcaption></figure><h3>What’s next?</h3><p>To find out more there’s a whole load of reading, modules, and docs — check some of these out:</p><ul><li><a href="https://www.guidepup.dev/">https://www.guidepup.dev/</a></li><li><a href="https://github.com/guidepup">https://github.com/guidepup</a></li></ul><p>Time to try out some e2e screen-reader tests?</p><p>Let me know your thoughts, questions, and opinions in the comments!</p><p>Till next time folks 👋</p><p><em>Originally published at </em><a href="https://dev.to/craigmorten/a11y-unlocked-screen-reader-automation-tests-3mc8"><em>https://dev.to</em></a><em> on October 16, 2022.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=788b5c6bac8d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testing Web Vitals With Cypress]]></title>
            <link>https://medium.com/@craigmorten/testing-web-vitals-with-cypress-60d927546062?source=rss-c6b59d857cb7------2</link>
            <guid isPermaLink="false">https://medium.com/p/60d927546062</guid>
            <category><![CDATA[cypress]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Craig Morten]]></dc:creator>
            <pubDate>Sun, 03 Apr 2022 10:49:14 GMT</pubDate>
            <atom:updated>2024-01-27T14:54:05.561Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>It is well understood that <a href="https://web.dev/why-speed-matters/">performance is a critical consideration</a> for any website which can have far reaching impacts on everything from customer satisfaction, SEO, and ultimately your bottom line. You cannot determine the success of performance work without the ability measure the results and compare to <a href="https://web.dev/performance-budgets-101/">performance budgets</a> — this calls for testing infrastructure to make sure you have the necessary visibility on metrics… introducing <a href="https://www.npmjs.com/package/cypress-web-vitals">cypress-web-vitals</a>.</p><p>cypress-web-vitals allows you to test against the Google Web Vital signals within your Cypress workflows through a new cy.vitals() custom command.</p><blockquote><em>Web Vitals is an initiative by Google to provide unified guidance for quality signals that are essential to delivering a great user experience on the web.</em></blockquote><blockquote><em>Reference: </em><a href="https://web.dev/vitals/"><em>https://web.dev/vitals/</em></a></blockquote><h3>Getting started</h3><p>In your project, install the dependencies:</p><pre>npm install --save-dev cypress-web-vitals cypress-real-events</pre><blockquote><em>Note: </em><em>cypress-web-vitals currently makes use of </em><em>cypress-real-events to click the page to calculate first input delay. Hence it is needed as a peer-dependency.</em></blockquote><p>Within you support commands file (normally cypress/support/commands.js), add this one liner to get setup:</p><pre>import &quot;cypress-web-vitals&quot;;</pre><p>And now you’re set to get going with Web Vital performance budget tests in your Cypress workflow! 🎉</p><p>Add you first test like so:</p><pre>describe(&quot;Web Vitals&quot;, () =&gt; {<br>  it(&quot;should pass the meet Google&#39;s &#39;Good&#39; thresholds&quot;, () =&gt; {<br>    cy.vitals({ url: &quot;https://www.google.com/&quot; });<br>  });<br>});</pre><p>You are now set up to test against all of the Web Vitals using Google’s “Good” threshold values:</p><ul><li><a href="https://web.dev/lcp/">Largest contentful paint (LCP)</a> — 2500.</li><li><a href="https://web.dev/fid/">First input delay (FID)</a> — 100.</li><li><a href="https://web.dev/cls/">Cumulative layout shift (CLS)</a> — 0.1.</li><li><a href="https://web.dev/fcp/">First contentful paint (FCP)</a> — 1800.</li><li><a href="https://web.dev/time-to-first-byte/">Time to first byte (TTFB)</a> — 600.</li></ul><h3>Customise your tests</h3><p>You can further customise your tests through additional optional configuration which is passed to the cy.vitals(webVitalsConfig) call:</p><ul><li>Optional url: string - The url to audit. If not provided you will need to have called cy.visit(url) prior to the command.</li><li>Optional firstInputSelector: string - The element to click for capturing FID. The first matching element is used. Default: &quot;body&quot;.</li><li>Optional thresholds: object - The thresholds to audit the Web Vitals against. If not provided Google&#39;s &quot;Good&quot; thresholds will be used. If provided, any missing Web Vitals signals will not be audited.</li><li>Optional vitalsReportedTimeout: number - Time in ms to wait for Web Vitals to be reported before failing. Default: 10000.</li></ul><p>For example:</p><pre>// Use the `main` element for clicking to capture the FID.<br>cy.vitals({ firstInputSelector: &quot;main&quot; });<br><br>// Test the page against against a CLS threshold of 0.2.<br>cy.vitals({ thresholds: { cls: 0.2 } });</pre><p>For more details on usage refer to the <a href="https://www.npmjs.com/package/cypress-web-vitals#api">API docs</a>.</p><h3>How does it work?</h3><ol><li>The url is visited with the HTML response intercepted and modified by Cypress to include the <a href="https://github.com/GoogleChrome/web-vitals">web-vitals</a> script and some code to record the Web Vitals values.</li><li>Several elements (if exist) starting with the provided element (based on firstInputSelector) are then clicked in quick succession to simulate a user clicking on the page. Note: if choosing a custom element, don&#39;t pick something that will navigate away from the page otherwise the plugin will fail to capture the Web Vitals metrics.</li><li>The audit then waits for the page load event to allow for the values of LCP and CLS to settle; which are subject to change as different parts of the page load into view.</li><li>Next the audit simulates a page visibility state change <a href="https://www.npmjs.com/package/web-vitals#basic-usage">which is required for the CLS Web Vital to be reported</a>.</li><li>The audit then attempts to wait for any outstanding Web Vitals to be reported for which thresholds have been provided.</li><li>Finally the Web Vitals values are compared against the thresholds, logging successful results and throwing an error for any unsuccessful signals. <em>Note: if the audit was unable to record a Web Vital then it is logged, but the test will not fail.</em></li></ol><h3>Testing sites in the wild</h3><p>Here are some example test runs against FAANG homepages to see cypress-web-vitals in action:</p><h3>Facebook</h3><pre>cy.vitals({ url: &quot;https://www.facebook.com/&quot; });</pre><figure><img alt="Cypress Web Vitals test against the Facebook landing page. All metrics below Google’s “Good” thresholds." src="https://cdn-images-1.medium.com/max/1024/1*Eg6BAV9BXNuBg1p2jpurLw.png" /></figure><h3>Amazon</h3><pre>cy.vitals({ url: &quot;https://www.amazon.com/&quot; });</pre><figure><img alt="Cypress Web Vitals test against the Amazon home page. All metrics below Google’s “Good” thresholds." src="https://cdn-images-1.medium.com/max/1024/1*iDccSpX8W8pvZrf1cKkdMA.png" /></figure><h3>Netflix</h3><pre>cy.vitals({<br>  url: &quot;https://www.netflix.com/gb/&quot;,<br>  firstInputSelector: &quot;.our-story-card-title&quot;,<br>});</pre><figure><img alt="Cypress Web Vitals test against the Netflix landing page. All metrics below Google’s “Good” thresholds." src="https://cdn-images-1.medium.com/max/1024/1*prg17oN7GjT8Sc0_7R3LHw.png" /></figure><p>For Netflix we have had to introduce a custom element choice for the simulated “first click”. Even though <a href="https://web.dev/fid/#what-if-an-interaction-doesn&#39;t-have-an-event-listener">first input delay can be measured when in cases where there is no event listener registered to the element</a>, there are scenarios where clicking the body doesn&#39;t cut it. Some good examples of elements that will reliably trigger an FID metric are:</p><ul><li>Text fields, checkboxes, and radio buttons (&lt;input&gt;, &lt;textarea&gt;)</li><li>Select dropdowns (&lt;select&gt;)</li><li>Links (&lt;a&gt;)</li></ul><p>In order to ensure the Web Vitals can be tested against, it is best to try find an element that reliably triggers an FID, but won’t navigate away from the page (perhaps avoid &lt;a&gt;!).</p><h3>Google</h3><pre>cy.vitals({ url: &quot;https://www.google.com/&quot; });</pre><figure><img alt="Cypress Web Vitals test against the Google home page. All metrics below Google’s “Good” thresholds." src="https://cdn-images-1.medium.com/max/1024/1*Jz0HNHULqxJ8rFUbWwnnEA.png" /></figure><h3>Wrap-up</h3><p>Using any awesome performance testing tooling lately? Tried out <a href="https://www.npmjs.com/package/cypress-web-vitals">cypress-web-vitals</a> on your site and have results to share? Got any thoughts, queries, or questions? Leave a comment!</p><p>That’s all folks! 🚀</p><p><em>Originally published at </em><a href="https://dev.to/craigmorten/web-vitals-cypress-testing-3bpj"><em>https://dev.to</em></a><em> on April 3, 2022.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=60d927546062" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>