<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.6">Jekyll</generator><link href="https://engineering.rei.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://engineering.rei.com/" rel="alternate" type="text/html" /><updated>2025-02-03T23:40:12+00:00</updated><id>https://engineering.rei.com/feed.xml</id><title type="html">REI Co-op Engineering</title><subtitle>We're into open source and the outdoors. And in the spirit of giving back to the community (that's you), REI Co-op's engineers are sharing awesome ideas, thoughts, guides and other works related to our open source software.
</subtitle><entry><title type="html">Server Driven UI: Prepared for the Unknown</title><link href="https://engineering.rei.com/mobile/server-driven-ui.html" rel="alternate" type="text/html" title="Server Driven UI: Prepared for the Unknown" /><published>2024-12-31T00:00:00+00:00</published><updated>2024-12-31T00:00:00+00:00</updated><id>https://engineering.rei.com/mobile/server-driven-ui</id><content type="html" xml:base="https://engineering.rei.com/mobile/server-driven-ui.html">&lt;h2 id=&quot;you-wont-know-till-you-get-there&quot;&gt;You won’t know ‘till you get there&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This blog post is the second half of a 2-part series on a new tool that REI’s Store Technology team has been working on for the last few months - the “Price Change Tool”.
You can find Part 1 &lt;a href=&quot;https://engineering.rei.com/culture/store-technology.html&quot;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For folks who haven’t read Part 1, this post comes from the Ascent team, a small-but-mighty product team that builds the iOS app used by REI store employees. 
Ascent includes features like product search, inventory lookup, and restocking task management. 
The recent addition of the Price Change Tool gives store employees a digital tool to help re-label items that change price.&lt;/p&gt;

&lt;p&gt;At a high level, the Price Change Tool provides two main value-adds over the previous pen-and-paper solution:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;An interface that makes properly executing a price change task simple and intuitive.&lt;/li&gt;
  &lt;li&gt;The ability to filter and sort price change tasks to match your unique work environment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks to a lot of prototyping work from our designer, Alyssa, we had a clear answer for #1 early into development, but a solution for #2 proved to be more illusive.
This is because at REI, we have over 150 stores, each with a unique combination of square footage, staffing, warehouse to sales-floor ratio, traffic, age of the store, etc.
This wide variety of cases made it daunting to design a solution that was supposed to work for all users across the enterprise.
Even though we surveyed dozens of employees, we still weren’t confident that our initial release would cover all employee’s needs. 
We knew this was something we would need several iterations to get right, and we needed a way to do that quickly enough to not leave some employees without the support they needed.&lt;/p&gt;

&lt;h2 id=&quot;how-do-you-plan-for-the-unknown&quot;&gt;How do you plan for the unknown?&lt;/h2&gt;

&lt;p&gt;Luckily, this tool was truly a &lt;a href=&quot;https://en.wikipedia.org/wiki/Greenfield_project&quot;&gt;greenfield project&lt;/a&gt; for us, so we were able to plan ahead for this kind of uncertainty and afford ourselves options ahead of time.
The main way we did this was with our choice to leverage server-driven UI (SDUI). 
In short, SDUI is all about giving the backend as much of the control over how the UI looks as possible, so you can redesign the way the app looks without having to push an iOS app update &lt;em&gt;(because who actually updates their mobile apps on a regular basis!?)&lt;/em&gt; 
While I wouldn’t say we fully embraced SDUI (like &lt;a href=&quot;https://medium.com/airbnb-engineering/a-deep-dive-into-airbnbs-server-driven-ui-system-842244c5f5&quot;&gt;Airbnb&lt;/a&gt; has), we did choose to make UI components server-configurable wherever we were sufficiently unsure of our solution.
As I mentioned before, filtering and sorting was really the place where we didn’t have the confidence to bake a single solution into our iOS app release.&lt;/p&gt;

&lt;h2 id=&quot;implementing-sdui-in-the-price-change-tool&quot;&gt;Implementing SDUI in the Price Change Tool&lt;/h2&gt;

&lt;p&gt;In many ways, the API we created for the price change tool is just a really simple search interface. 
While it may not have a text search bar, we had to build a way for users to create custom queries that would search over the space of all price change activities.&lt;/p&gt;

&lt;p&gt;Luckily, here at REI, we have two entire teams dedicated to search, so we asked for their wisdom on how to get started on such an endeavor.&lt;/p&gt;

&lt;p&gt;Their advice: &lt;em&gt;“you don’t want to set up an entire Solr instance for what you’re building.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While there is a lot of features that come out-of-box with Solr (or Elasticsearch), they warned us that the amount of time and effort it takes to maintain that infrastructure wouldn’t be worth it for us. 
So now armed with what &lt;em&gt;not&lt;/em&gt; to do, we set off to find a way to query our modestly sized database (~1M records) in a maintainable and performant manner.&lt;/p&gt;

&lt;h3 id=&quot;querydsl&quot;&gt;Querydsl&lt;/h3&gt;

&lt;p&gt;Since our backend for the Price Change Tool is written in Java, and we connect to a PostgreSQL database through JPA, &lt;a href=&quot;https://querydsl.com/&quot;&gt;Querydsl&lt;/a&gt; stood out as a good option.
Querydsl is an open source library that allows you to construct SQL queries in a simple Java object syntax. 
We had our concerns about performance when dynamically generating custom queries using Querydsl, but since launch, we’ve seen consistent &amp;lt;10ms P95 response times for our most common queries.&lt;/p&gt;

&lt;p&gt;Overall, we’ve been very happy with the decision to use Querydsl. 
Most of the effort of setting up Querydsl for this use case came from designing a simple query language that we could translate filtering/sorting selections into Querydsl. 
Below are some high level notes on how the integration works, as well as a bonus section about making Querydsl query typing stronger (an area we found error-prone during development).&lt;/p&gt;

&lt;h3 id=&quot;example-implementation-query-parameters---sql&quot;&gt;Example Implementation: Query Parameters -&amp;gt; SQL&lt;/h3&gt;

&lt;p&gt;Here’s a simple example of how we can take an arbitrary set of query params and convert them to SQL.&lt;/p&gt;
&lt;h4 id=&quot;sample-url&quot;&gt;Sample URL&lt;/h4&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/rs/price-changes/list?filter=brand:Patagonia;department:Basics,Mens%20Outerwear
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;converting-filter-values-to-a-jpa-query&quot;&gt;Converting Filter Values to a JPA query&lt;/h4&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FacetKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BRAND&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Patagonia&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DEPARTMENT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Basics&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Mens Outerwear&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;querydslPredicate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FacetKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;entrySet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BRAND&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;querydslPredicate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;QPriceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;priceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;brand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())));&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DEPARTMENT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;querydslPredicate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;QPriceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;priceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;department&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceChangeActivityRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;querydslPredicate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;the-resulting-sql-query&quot;&gt;The Resulting SQL Query&lt;/h4&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price_change_activity&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brand&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Patagonia'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;department&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Basics'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Mens Outerwear'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Of course, this example doesn’t show what it takes to parse the query string into the appropriate data structure, but that wiring is fairly trivial and only needs to be implemented once. 
Adding new filters simply becomes a matter of adding a new &lt;code class=&quot;highlighter-rouge&quot;&gt;FacetKey&lt;/code&gt; case to the switch statement, and then exposing it as an option to the user.&lt;/p&gt;

&lt;h3 id=&quot;exposing-query-options-with-sdui&quot;&gt;Exposing Query Options with SDUI&lt;/h3&gt;

&lt;p&gt;Once we had the ability to convert query params to SQL, we just needed to tell the frontend client how to construct the query string. 
This is where the power of the server-driven UI pattern comes into play. 
We designed a flexible JSON schema that could allow us to create the filtering/sorting screens entirely from backend JSON responses. 
Using that same &lt;code class=&quot;highlighter-rouge&quot;&gt;queryString&lt;/code&gt; from above as an example, here is the corresponding part of the API response which the client uses to generate the view:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;filters&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Brand&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;selected&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resetQueryString&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;?filter=department:Basics,Mens%20Outerwear&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;options&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Patagonia&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;selected&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;queryString&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;?filter=department:Basics,Mens%20Outerwear&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The North Face&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;selected&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;queryString&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;?filter=brand:The%20North%20Face;department:Basics,Mens%20Outerwear&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;While this example is &lt;em&gt;much&lt;/em&gt; simpler than what we use in the Price Change Tool, it still shows how the backend can suggest a query that will give predictable results to the frontend, so it can display it in a nice way for the user to select. 
In addition to &lt;code class=&quot;highlighter-rouge&quot;&gt;label&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;selected&lt;/code&gt;, we have grown this schema to include all kinds of things like &lt;code class=&quot;highlighter-rouge&quot;&gt;doNotReorder&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;hasDefaultSelection&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;showInQuickFilterRow&lt;/code&gt;. 
Each of these variables drive a different UI treatment that is hardcoded into the iOS client.&lt;/p&gt;

&lt;p&gt;By following this pattern, we can make the iOS client forward-compatible with data the backend may not be ready to supply. 
We actually did this for our second phase of our pilot release, where I sent our frontend developer several handwritten JSON responses that mimicked what the backend response would someday look like.
This way, we were able to support a wide variety of UI layouts without the need for disruptive frontend code changes.&lt;/p&gt;

&lt;h2 id=&quot;so-did-it-work&quot;&gt;So did it work?&lt;/h2&gt;

&lt;h3 id=&quot;impact-to-our-users&quot;&gt;Impact to our users&lt;/h3&gt;
&lt;p&gt;Since the time we released the tool in our first round of pilot stores, we have been able to rapidly iterate on filtering and sorting options in response to user feedback. 
We have made over 20 changes to filtering/sorting, each one going live without requiring an app update.
In several cases, we have even been able to get a new filter into the hands of all users within a couple of days of receiving user feedback.&lt;/p&gt;

&lt;p&gt;Here are several iterations of the app in the last month as we have added more and more filter functionality to the app.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;
	&lt;img alt=&quot;Ascent app showing shop filter option removed&quot; src=&quot;/static/images/articles/server-driven-ui/ascent_filter_1.png&quot; style=&quot;max-width: 250px;&quot; /&gt;
    &lt;img alt=&quot;Ascent app showing department filter expanded with counts next to options&quot; src=&quot;/static/images/articles/server-driven-ui/ascent_filter_2.png&quot; style=&quot;max-width: 250px;&quot; /&gt;
    &lt;img alt=&quot;Ascent app showing many filter options added&quot; src=&quot;/static/images/articles/server-driven-ui/ascent_filter_3.png&quot; style=&quot;max-width: 250px;&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Furthermore, our team has started to see requests to improve filtering and sorting in other areas of the app that don’t leverage this pattern.
One of the greatest impacts I believe our team can have is giving our users hope that their technology frustrations can be solved, and they can be a part of that process! 
&lt;em&gt;(This is the theme of &lt;a href=&quot;https://engineering.rei.com/culture/store-technology.html&quot;&gt;Part 1&lt;/a&gt; of this series)&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;impact-to-our-team&quot;&gt;Impact to our team&lt;/h3&gt;

&lt;p&gt;From our team’s perspective, this has significantly reduced the time required to deliver features. Here’s a comparison of the process for making a change to filtering in an older part of our app:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Process&lt;/th&gt;
      &lt;th&gt;Steps&lt;/th&gt;
      &lt;th&gt;People Involved&lt;/th&gt;
      &lt;th&gt;Time Involved&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Previous Process&lt;/td&gt;
      &lt;td&gt;1. Make the backend change  &lt;br /&gt; 2. Make the frontend change  &lt;br /&gt; 3. Run the entire app through QA  &lt;br /&gt; 4. Deploy the app to stores (likely with a pilot release before enterprise-wide rollout)  &lt;br /&gt; 5. &lt;em&gt;(If something went wrong)&lt;/em&gt; Push the old iOS app version out to all devices in the fleet&lt;/td&gt;
      &lt;td&gt;~10 people&lt;/td&gt;
      &lt;td&gt;Min: A few days  &lt;br /&gt; Max: A normal monthly release cycle&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;New (SDUI) Process&lt;/td&gt;
      &lt;td&gt;1. Make the backend change  &lt;br /&gt; 2. Use our mature CI/CD pipeline to ensure a suite of tests pass before being released to prod &lt;br /&gt; 3. &lt;em&gt;(If something went wrong)&lt;/em&gt; A “bad” change can be rolled back in under 10 minutes&lt;/td&gt;
      &lt;td&gt;1 person&lt;/td&gt;
      &lt;td&gt;Min: 1 hour  &lt;br /&gt; Max: A few days&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Overall, this design pattern frees our frontend dev and QA resources up to work on major UI changes and features, not plumbing minor data changes into an existing interface.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;bonus-section-explicitly-typed-queries-in-querydsl&quot;&gt;Bonus Section: Explicitly Typed Queries in Querydsl&lt;/h2&gt;
&lt;p&gt;Querydsl is great at producing queries that give you strongly typed parameters and responses, but unfortunately the queries themselves are not tied to a specific queryable type.
In the Price Change Tool, we frequently need to map a &lt;code class=&quot;highlighter-rouge&quot;&gt;queryString&lt;/code&gt; into queries for two different tables.
Specifically, these queries may be for a table containing &lt;code class=&quot;highlighter-rouge&quot;&gt;PriceChangeActivity&lt;/code&gt; entities, or &lt;code class=&quot;highlighter-rouge&quot;&gt;StyleColorGroup&lt;/code&gt; entities. 
Since the queries aren’t explicitly typed, it’s easy to accidentally do things like this:&lt;/p&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QPriceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;priceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;storeId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;11&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; 
&lt;span class=&quot;nc&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QStyleColorGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;styleColorGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;123456&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;be2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// compiler allows this&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;priceChangeActivityRepo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;be3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// runtime error &quot;Cannot find column 'style' in table 'price_change_activity'&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make it harder to get caught up in this pitfall, I wrote a wrapper class called &lt;code class=&quot;highlighter-rouge&quot;&gt;QueryDslExpression&lt;/code&gt; that attaches a JPA entity type to the query. 
Here’s how that looks in practice:&lt;/p&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PriceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qde1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;QPriceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;priceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;storeId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;11&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StyleColorGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qde2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;QStyleColorGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;styleColorGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;123456&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;PriceChangeActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qde3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;be2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// compiler error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This class is trivial enough to show in its entirety here:&lt;/p&gt;
&lt;h4 id=&quot;querydslexpression&quot;&gt;QueryDslExpression&lt;/h4&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslQueryableEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;expression&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslQueryableEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslQueryableEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BooleanExpression&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newBuilder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;newBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newBuilder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BooleanBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;newBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslExpression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Predicate&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;asPredicate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;querydslqueryableentity&quot;&gt;QueryDslQueryableEntity&lt;/h4&gt;
&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryDslQueryableEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// JPA @Entity that you want to query must implement this interface&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>David Allison</name><email>dalliso@rei.com</email></author><category term="mobile" /><category term="mobile" /><category term="sdui" /><category term="server-driven" /><category term="employee" /><category term="querydsl" /><summary type="html">You won’t know ‘till you get there</summary></entry><entry><title type="html">Store Technology: Building Alongside your Users</title><link href="https://engineering.rei.com/culture/store-technology.html" rel="alternate" type="text/html" title="Store Technology: Building Alongside your Users" /><published>2024-09-27T00:00:00+00:00</published><updated>2024-09-27T00:00:00+00:00</updated><id>https://engineering.rei.com/culture/store-technology</id><content type="html" xml:base="https://engineering.rei.com/culture/store-technology.html">&lt;p&gt;&lt;em&gt;The joy of developing solutions for, and with, real people&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;on-the-path-to-product-led&quot;&gt;On the path to “Product-Led”&lt;/h2&gt;

&lt;p&gt;When working on a product development team, perhaps the most important question to answer is &lt;em&gt;“will this meet the customer’s needs?”&lt;/em&gt; 
Depending on the product you’re building and who your customer is, it may be very difficult to answer this question. 
This question may be paired with other questions like &lt;em&gt;“what are the customer’s needs?”&lt;/em&gt; or &lt;em&gt;“who even are our customers?”&lt;/em&gt; 
While there are many opinions on how to most effectively answer these questions, REI technology made the decision a few years ago to embark on the journey towards a particular philosophy: “product-led”.&lt;/p&gt;

&lt;p&gt;The fundamental premise of product-led growth is that the product you develop should be the primary driver of your business. 
Now I hear what you’re saying, “REI is not a software company, your product isn’t an application”, and that is true! 
But we believe that a number of the core tenets of product-led growth can help us build better technology solutions, to real user problems, faster.&lt;/p&gt;

&lt;p&gt;If you want to learn more about the ideas behind product-led, empowered product teams, and a vision for extreme customer-centric solutions, I highly recommend reading this &lt;a href=&quot;https://www.svpg.com/empowered-product-teams/&quot;&gt;article by Marty Cagan&lt;/a&gt;. 
The goal of this blog post is not to sell you on product-led ideas, but I will frequently refer to ideas from Marty Cagan’s book &lt;a href=&quot;https://www.svpg.com/books/inspired-how-to-create-tech-products-customers-love-2nd-edition/&quot;&gt;Inspired&lt;/a&gt;, and I believe he has some great ideas on how to answer these above questions effectively.&lt;/p&gt;

&lt;h2 id=&quot;why-is-it-so-hard-to-know-what-the-user-wants&quot;&gt;Why is it so hard to know what the user wants?&lt;/h2&gt;

&lt;h3 id=&quot;problem-1-the-user-is-in-a-galaxy-far-far-away&quot;&gt;Problem #1: The user is in a galaxy far, far away&lt;/h3&gt;
&lt;p&gt;If you are on a product team reading this, you already have a list in your head a mile long about why it’s difficult to hear real feedback from your users.
Maybe your list includes things like:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Focus groups are expensive&lt;/li&gt;
  &lt;li&gt;The only feedback we get in our in-app feedback channel is vague, and we can’t follow up to ask more questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could have the most agile team in the world and still struggle with how to clear these hurdles. 
For most technology products, these are real constraints that mean developing quality solutions requires a good mix of intuition, research, and a lot of resources.
And that’s just the way it is… &lt;em&gt;or is it?&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;problem-2-competition&quot;&gt;Problem #2: Competition&lt;/h3&gt;
&lt;p&gt;In the vast majority of software solutions, your user has the choice to go with the competition. 
This inconvenient fact means you can spend lots of time building something that you think will meet your user’s needs and, in the end, you will learn that it wasn’t enough to drive conversion.
The product-led growth mindset aims to solve this by building the best-darned-product your user has ever seen… but sometimes you just get it wrong. 
Sometimes your journey of learning what a user wants gets cut short by them walking away.&lt;/p&gt;

&lt;h2 id=&quot;what-if-we-didnt-have-to-worry-about-either-of-those-issues&quot;&gt;What if we didn’t have to worry about either of those issues?&lt;/h2&gt;

&lt;p&gt;Imagine a world where product teams could build solutions without competition, could talk to their users whenever they wanted, and could build persistent relationships with individual users. 
That would almost be &lt;em&gt;too&lt;/em&gt; easy, right?&lt;/p&gt;

&lt;p&gt;Enter the field of employee tools.&lt;/p&gt;

&lt;p&gt;Anyone who has ever worked a job that required the use of employee-facing software just rolled their eyes at that last statement. 
We all have stories of a legacy tool that is mission-critical to our business and has been left on life-support for over a decade. 
Or maybe you have a story of a normal, every day task, that requires you to endlessly jump back and forth from one system to another. 
Chances are, this tool you’re imagining right now, is something you long ago stopped hoping would get better. 
You don’t file IT tickets for it anymore because you know “this is just the way it is”, or you “have a work-around”. 
If asked, you probably would assume there’s no one at HQ that even thinks about this software.&lt;/p&gt;

&lt;p&gt;It seems at least a little ironic that the one place where product teams have nearly unlimited access, to a user base that has no access to competing solutions, is the same place where solutions so often get neglected. 
But now is where I am going to intentionally avoid diving into “why” this phenomenon is so common. 
To be honest, I don’t have a good answer as to why so many organizations let employee tools fade into unsupported legacy software, but what I do have to offer is a vignette of how cool Store Technology &lt;em&gt;can&lt;/em&gt; be, if handed over to an &lt;a href=&quot;https://www.svpg.com/empowered-product-teams/&quot;&gt;empowered product team&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ascent-built-with-employees-for-employees&quot;&gt;Ascent: built with employees, for employees&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Fall in love with the problem, not the solution. - Uri Levine&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;

&lt;p&gt;Ascent is REI’s in-house employee-facing iOS native app that is used by the “green-vests” to power sales floor operations. 
When you walk up and ask “Do you have this tent in-stock?”, the tool they use to answer your question is likely Ascent. 
Beyond inventory and product information capabilities, we have added a number of bespoke tools to Ascent that retail employees use to aid in their day-to-day operations.
For the purposes of this article, we will focus on the “Price Change Tool”, a tool we have recently developed.&lt;/p&gt;

&lt;p&gt;The Price Change Tool does exactly what it sounds like; it helps employees re-sticker products when their prices change. 
Previously, price-change days involved printed pieces of paper with a list of SKUs and (stale) inventory data (it’s hard to do a cache-clear on a paper list!).
Employees would walk around the store, typing SKUs by-hand into Ascent, relying on handwritten notes, symbols, or highlighters to keep track of progress.&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;
	&lt;img alt=&quot;Example of the old paper price change report&quot; src=&quot;/static/images/articles/store-technology/old-price-change-list.jpeg&quot; class=&quot;article-image-full-width&quot; style=&quot;max-width: 481px;&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Even if this wasn’t explicitly unspoken among our team, I think it was pretty universally thought &lt;em&gt;“how hard could it be to put a list of SKUs into an app?”&lt;/em&gt;
Spoiler: it can be really hard! The amount of complexity that you can introduce with a pen and paper can be quite difficult to reproduce in an easy-to-use digital tool.&lt;/p&gt;

&lt;h3 id=&quot;research-pt-1-the-listening-tour&quot;&gt;Research Pt. 1: The Listening Tour&lt;/h3&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;
	&lt;img alt=&quot;Members of the Ascent team on a listening tour in Denver, CO&quot; src=&quot;/static/images/articles/store-technology/listening-tour-denver.jpg&quot; style=&quot;max-width: 481px;&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;One of the very first steps our product team takes when tackling a problem is to go talk to our real users, in stores.
This can look different from one problem to the next, but the last few major endeavors we have embarked on has started with what we call “a listening tour.” 
This consists of members of our team (ideally, representatives from at least Product, Design, and Engineering) traveling to a handful of stores across a region and asking them about their current process.&lt;/p&gt;

&lt;p&gt;When planning a listening tour, we intentionally try to choose a collection of stores we’ve never been to before, because part of the value of this trip is &lt;em&gt;building new relationships with store employees.&lt;/em&gt; 
As I alluded to above, it is common for employees to think “no one at HQ is working to make technology better… so why would I provide feedback” 
It is &lt;em&gt;so cool&lt;/em&gt; to have conversations with store employees and see their eyes light up when you say you’re working on a solution to a problem they’ve faced for years. 
Not just is there excitement that &lt;em&gt;this&lt;/em&gt; problem may get fixed, but you see a glimmer of hope that maybe other problems they have can be addressed as well. 
For every listening tour we’ve done, we have immediately seen a remarkable uptick in in-app feedback from that region. 
At this point, we haven’t even started to solve the problem at hand, but we have already begun to build a legion of power-users who will provide honest feedback when we release something good or bad; all because they now see we’re real humans.&lt;/p&gt;

&lt;h3 id=&quot;research-pt-2-frequent-check-ins-with-users&quot;&gt;Research Pt. 2: Frequent Check-Ins with Users&lt;/h3&gt;
&lt;p&gt;Once our minds are swirling with ideas from big stores, small stores, stores with mature processes, and stores who yearn for a more efficient process, we start iterating on a prototype. 
We engineers, our product manager, and our designer all work together on validating this design (not just our designer!) and we are quick to reach back out to stores (in-person or over chat) to challenge assumptions. 
I can’t overstate this part of the process enough - this is the real secret sauce that allows our team to work in a truly agile way. 
It would be all too easy to fall into a waterfall pattern of collecting requirements from users in the initial tour and then never talk to them again until release time. &lt;em&gt;Don’t do this!!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Throughout the months of building the Price Change Tool, we had barely a week go by where at least one of us wasn’t in a store talking to users. 
This process meant that by the time we launched, we had a handful of users in 3-4 stores who had seen every major iteration of the design throughout the development process.&lt;/p&gt;

&lt;h3 id=&quot;bonus-standard-operating-procedures&quot;&gt;Bonus: Standard Operating Procedures&lt;/h3&gt;
&lt;p&gt;In the past, we haven’t always done a good job at releasing a standard operating procedure (SOP) with our new tools. 
This leads our 150+ stores to develop 150+ ways of using the tool… with varying levels of success. 
Other times, our continuous-improvement (CI) partners would later write an SOP that works with the tool… even if the tool doesn’t support the most efficient way of completing a task. 
With the Price Change Tool, we tried something a little less waterfall - we embedded one of our CI partners directly into our product team. 
They joined for our daily standups, the listening tour, story refinement sessions, and had an active voice in deciding how we would build this tool.&lt;/p&gt;

&lt;p&gt;The lesson we learned: &lt;em&gt;we’re going to do this for all future tools we build.&lt;/em&gt; As we release this tool to the entire enterprise, we can refer our users to a cohesive SOP that was built hand-in-hand with the tool. 
We have built the tool to flow in a way that encourages users to follow the SOP and intentionally added carefully placed tooltips throughout the app to ease the transition from paper to digital tool.&lt;/p&gt;

&lt;h3 id=&quot;the-solution-pt-1-pilot-releases&quot;&gt;The Solution Pt. 1: Pilot Releases&lt;/h3&gt;

&lt;p&gt;We’ve spent months building this tool, we’re as sure as we can be that it’s going to work, so time to let it rip across all our stores? &lt;em&gt;Nope!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the last few years, our team has developed a carefully crafted pilot program. 
For smaller releases, we release the app to a handful of stores, let it bake for a week, build any show-stoppers into a hot-fix release or the enterprise release, and then we release to the enterprise. 
For bigger releases, like the Price Change Tool, we have developed a multiphase pilot plan.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Phase 1: We release the tool to a handful of stores, with a member of our team in each store. (&lt;em&gt;*while constantly iterating on feedback*&lt;/em&gt;)&lt;/li&gt;
  &lt;li&gt;Phase 2: We release the tool to more stores, including stores from the listening tour, and other stores within the vicinity of our team members, so they can be in-store. (&lt;em&gt;*while constantly iterating on feedback*&lt;/em&gt;)&lt;/li&gt;
  &lt;li&gt;Enterprise go-live: let it rip!&lt;/li&gt;
&lt;/ul&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;
	&lt;img alt=&quot;A store employee uses the price change tool in pilot&quot; src=&quot;/static/images/articles/store-technology/tool-in-action.jpg&quot; style=&quot;max-width: 481px;&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;This multiphase approach allows us ample time to iterate on backend and frontend changes, tweak the SOP, and see how users react to the tool.&lt;/p&gt;

&lt;p&gt;It’s sometimes difficult to know if a user pain-point is a real long-term issue, or just a side effect of transition, but building the tool alongside our users gives us some unique insight into that problem. 
Since we have some users that have seen this tool for months (as we’ve built it), and some users that see it for the first time on day one of the pilot, we have some data to draw on as we try to make this distinction.&lt;/p&gt;

&lt;p&gt;This pilot approach also gives our team a real chance to use the tool. 
We intentionally scheduled the pilots to include days with lots of price changes, so we can get in the store and feel how frustrating it is to do flow X or how nice it would be to have improvement Y.&lt;/p&gt;

&lt;h3 id=&quot;the-solution-pt-2-rapid-iteration&quot;&gt;The Solution Pt. 2: Rapid Iteration&lt;/h3&gt;
&lt;p&gt;It is a little odd that this entire engineering blog post hasn’t had any reference to code yet… don’t worry! 
There’s a lot of cool innovation that went into delivering a tool that gives us flexibility in responding to feedback after we go live in stores. 
But as this post is already quite long, I am going to post that in a &lt;a href=&quot;https://engineering.rei.com/mobile/server-driven-ui.html&quot;&gt;separate blog entry&lt;/a&gt; on server-driven design. 
Stay tuned!&lt;/p&gt;

&lt;h2 id=&quot;the-good-part&quot;&gt;The Good Part&lt;/h2&gt;
&lt;p&gt;So why does this process matter? 
Why am I writing this post at all?&lt;/p&gt;

&lt;p&gt;My hope is that this story inspires even a single internal-facing product team to change their ways of working to bring their users radically deep into the development process. 
I truly believe this process results in employees being more satisfied (or at least less frustrated) with their jobs, less production issues in tools, and a better developer experience.&lt;/p&gt;

&lt;p&gt;But as for me, when I look back on this entire build, the coolest part of all of this boils down to a single moment that came on the first big price change day in Phase 1 of our pilot release.&lt;/p&gt;

&lt;p&gt;It was the end of the day, and I found myself and 4 green-vests crammed in a small room in the back office of the Roseville, CA store (shout-out to the awesome folks at store 74!). 
We were all tired, half of us sitting on the floor, having all spent a considerable part of our day using the tool, debugging issues, and creating lists of things that we thought really needed to be changed before the enterprise go-live. 
Even though we were all exhausted, that conversation was at least as in-depth as any story refinement session we’ve ever had as a product team. 
I was furiously taking incoherent notes on my laptop as ideas were flying left and right about how to fix issues, streamline workflows, and add net-new features none of us had considered before. 
The store employees &lt;em&gt;truly had a seat at the table&lt;/em&gt; to make decisions about the tool we would release a couple of weeks later to the entire company.&lt;/p&gt;

&lt;p&gt;That moment warms my heart not just because I’m a developer that gets the privilege to build cool stuff alongside my users, but because I started my career at REI as a green-vest. 
I remember being in their shoes, frustrated about an out-dated tool/process that feels like it may never change. 
I remember the first time I talked to a real person from REI IT, and the hope that it brought me that we &lt;em&gt;could&lt;/em&gt; make things better.
And now, here I am, still working shoulder-to-shoulder with my fellow green-vests, building solutions to &lt;em&gt;real&lt;/em&gt; problems that affect thousands of &lt;em&gt;real&lt;/em&gt; people every day.&lt;/p&gt;</content><author><name>David Allison</name><email>dalliso@rei.com</email></author><category term="culture" /><category term="employee" /><category term="retail" /><category term="tools" /><category term="agile" /><category term="product-led" /><category term="stores" /><summary type="html">The joy of developing solutions for, and with, real people</summary></entry><entry><title type="html">XCUITest Automation: Page Components for iOS Test Automation</title><link href="https://engineering.rei.com/mobile/xcuitest-page-components.html" rel="alternate" type="text/html" title="XCUITest Automation: Page Components for iOS Test Automation" /><published>2024-01-29T00:00:00+00:00</published><updated>2024-01-29T00:00:00+00:00</updated><id>https://engineering.rei.com/mobile/xcuitest-page-components</id><content type="html" xml:base="https://engineering.rei.com/mobile/xcuitest-page-components.html">&lt;h2 id=&quot;creating-stable-maintainable-user-interface-test-automation-in-swift&quot;&gt;Creating Stable, Maintainable User Interface Test Automation in Swift&lt;/h2&gt;

&lt;p&gt;When creating test automation to validate the performance and behavior of a complex software application, adhering to solid engineering practices will produce stable, maintainable tests. This is especially true when driving the application through its user interface. Page Object Model (POM) design patterns provide a solid framework to achieve this objective.&lt;/p&gt;

&lt;h3 id=&quot;functional-grouping-with-page-components&quot;&gt;Functional grouping with page components&lt;/h3&gt;

&lt;p&gt;In the &lt;a href=&quot;xcuitest-page-object-models.html&quot;&gt;previous article&lt;/a&gt;, we showed how POM design patterns can be applied to implementing automated interactions with iOS applications through the XCUITest framework. Modeling each view with a single comprehensive page class is adequate for simple views, but factoring the features of more complex view into functional grouping can simplify interactions with these features.&lt;/p&gt;

&lt;p&gt;Modeling an application based solely on its views produces a very flat model. It’s quite common for a view to contain groups of elements that are logically associated (e.g. - billing address on an order information view). It’s also common to encounter views with multiple occurrences of an element grouping (e.g. - item tiles on a search results view). Factoring these grouping out into &lt;strong&gt;page components&lt;/strong&gt; can greatly enrich your models, presenting a conceptual framework that automation developers will recognize.&lt;/p&gt;

&lt;p&gt;In the following example, the title block elements of the product details view are modeled in a page component:&lt;/p&gt;

&lt;h5 id=&quot;productdetailsview-excerpt&quot;&gt;ProductDetailsView excerpt&lt;/h5&gt;
&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  ProductView.swift&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  Copyright © 2024 REI. All rights reserved.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTest&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ProductDetailsView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NavigationHub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;titleBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ProductTitleBlock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ProductTitleBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}()&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// -._.-'¯'-._.-'¯'-._.-'¯'-._.-'¯'-._.-'¯'-._.-'¯'-._.-'¯'-&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;producttitleblock&quot;&gt;ProductTitleBlock&lt;/h5&gt;
&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  ProductTitleBlock.swift&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  Copyright © 2024 REI. All rights reserved.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTest&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ProductTitleBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;product_brand&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;product_title&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;style_or_sku&lt;/span&gt;
        
        &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;product_brand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TitleBlock.productBrand.label&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;product_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TitleBlock.productName.label&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;style_or_sku&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;TitleBlock.styleOrSku.label&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;
    
    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getProductId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;style_or_sku&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the preceding code, note how the &lt;strong&gt;ProductTitleBlock&lt;/strong&gt; page component is defined as a lazy-initialize property of the main &lt;strong&gt;ProductDetailsPage&lt;/strong&gt; class. The page component itself declares a locator enumeration that defined three title block elements, with the &lt;code class=&quot;highlighter-rouge&quot;&gt;getProductId()&lt;/code&gt; function providing access to the corresponding content.&lt;/p&gt;

&lt;p&gt;Note that the context used to locate the product ID element is derived from the component’s parent (the product details view). In this case, the search context for component elements is the target application, because there’s no specific container that groups the title block elements. However, it’s common for components to represent specific sub-contexts - a scroll view for a multi-item collection or a cell that contains one of these items. More on search contexts and component collections later.&lt;/p&gt;

&lt;p&gt;The test class shown below demonstrates an how an automated test might use this page model to verify that the expected product ID is shown in the title block of the product details view.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MSAProductTabTests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTestCase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testSKUShownOnPDP_NoColor_NoSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sku&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;510-145-0012&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launchTheApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;productDetailsView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;searchBar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;searchForSingleItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sku&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;productDetailsView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;titleBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getProductId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sku&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Did not find product SKU: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sku&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As shown above, access to the product ID is provided by the &lt;strong&gt;ProductTitleBlock&lt;/strong&gt; page component via the &lt;em&gt;[titleBlock]&lt;/em&gt; property of the &lt;strong&gt;ProductDetailsView&lt;/strong&gt; object. The logical grouping provided by this structure helps to differentiate the features of this group relative to other components of the view. This can be extremely helpful when implementing tests that interact with complex views comprised of dozens of elements. Each component provides focused access to a small set of logically associated elements, while a flat model would inundate the test author with an unstructured collection of every element of the entire view.&lt;/p&gt;

&lt;h3 id=&quot;page-component-with-unique-search-context&quot;&gt;Page component with unique search context&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;ProductTitleBlock&lt;/strong&gt; page component model shown above illustrates a &lt;em&gt;“virtual component”&lt;/em&gt; - a page component with no associated container element. These sorts of components are great for organizing and logically segmenting the functionality of complex views. The presence of a unique container element that encapsulated the features of a page component can increase the cohesion, safety, and efficiency of your model by providing a limited scope for element selection - the &lt;strong&gt;search context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The following example demonstrates how to define and use a pre component search context:&lt;/p&gt;

&lt;h4 id=&quot;defining-search-context-for-singleton-page-component&quot;&gt;Defining search context for singleton page component&lt;/h4&gt;
&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  ProductInventoryBlockSubView.swift&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  MSAMobileApp-UITests&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  Created by John Comstock on 8/2/21.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  Copyright © 2021 REI. All rights reserved.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTest&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ProductAvailabilityHeader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NavigationHub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;onhand_value&lt;/span&gt;
        
        &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;otherElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AvailabilityHeader.container.element&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;onhand_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OnHand.availability.value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;
    
    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getNearbyQuantity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nearby_value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that the initializer receives a reference to the page or component that’s including the &lt;strong&gt;ProductAvailabilityHeader&lt;/strong&gt; component - the &lt;em&gt;parent&lt;/em&gt;. The initializer derives the component’s search context from the parent, and this becomes the context for element searches within the component itself. For example, the &lt;code class=&quot;highlighter-rouge&quot;&gt;getNearbyQuantity()&lt;/code&gt; function derives the locator to the nearby store quantity element relative to this context.&lt;/p&gt;

&lt;p&gt;It’s not uncommon for page components to be initialized with a pre-assembled search context. However, this distributes the responsibility for defining and handling the attributes and behaviors of the component to two different classes - the page component and its parent. Prefer confining all component-specific operations to the page component class itself. This increases cohesion and simplifies ongoing maintenance of the page model implementation.&lt;/p&gt;

&lt;h3 id=&quot;search-context-for-page-component-collection-entries&quot;&gt;Search context for page component collection entries&lt;/h3&gt;

&lt;p&gt;Search contexts aren’t required to be unique. It’s commonplace to encounter multiple inherently indistinguishable container elements that encapsulate functionally equivalent components (e.g. - search result items). When modeling these sorts of components, definitive identification of each component must typically derive from attributes of elements within the component. Ideally, however, each container element will provided a unique identifier. In the following example, the style identifier for the product presented in each search result component is stored in the &lt;em&gt;[value]&lt;/em&gt; property of the containing &lt;strong&gt;cell&lt;/strong&gt; element:&lt;/p&gt;

&lt;h5 id=&quot;defining-unique-search-contexts-for-page-component-collection-entries&quot;&gt;Defining unique search contexts for page component collection entries&lt;/h5&gt;
&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  SeaarchResult.swift&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//  Copyright © 2024 REI. All rights reserved.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTest&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;result_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;product_brand&lt;/span&gt;
        
        &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;styleId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;predicate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSPredicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;identifier == %@ AND value == %@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SearchResultConstants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RESULT_CELL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;styleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;matching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;predicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;element&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SearchResultConstants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RESULT_CELL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;product_brand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SearchResultCell.productBrandLabel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;styleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;
    
    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;styleId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as?&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;XCTFail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed extracting style ID from search result cell value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;styleId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;styleId&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;styleId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getProductBrand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;product_brand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;buildResultsList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;selector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;waitForExistence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;standardTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;XCTFail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to load Search Results&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;searchResults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SearchResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allElementsBoundByAccessibilityElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;searchResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SearchResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;searchResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;searchResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searchResults&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SearchResultConstants&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;RESULT_CELL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SearchResult.cell&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;SearchResult&lt;/strong&gt; class provides a static function that builds a collection of search result page components. Note how the &lt;code class=&quot;highlighter-rouge&quot;&gt;buildResultsList()&lt;/code&gt; function employs the no-argument form of the &lt;strong&gt;result_cell&lt;/strong&gt; constant to acquire a list of every search result container element in the view. The implementation iterates over the selected container elements via their accessibility identifiers, which are somewhat more durable than the references produced by &lt;code class=&quot;highlighter-rouge&quot;&gt;allElementsBoundByIndex&lt;/code&gt;. However, these references are still at risk of going stale when the view gets repopulated after the search results are scrolled or filters are applied.&lt;/p&gt;

&lt;p&gt;To avoid subsequent reference mismatch issues, the &lt;strong&gt;SearchResult&lt;/strong&gt; initializer produces a fully qualified reference by specifying the style ID as the argument for the &lt;strong&gt;result_cell&lt;/strong&gt; element locator constant. This ensures that we always select the search result we expect. If the specified element exists, the associated search result item is guaranteed to show the expected product. If the specified element isn’t found later (e.g. - excluded by an applied filter), we can be certain that the corresponding search result item is actually gone. Note that the initializer is specified as optional; if the style ID can’t be acquired with the specified element reference, the initializer returns &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt;. (This is academic, though, since the initializer also registers a test failure.)&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Page object design patterns provided many benefits when applied to user interface automation of iOS applications. Modeling related groups of elements as page components can make your designs even more cohesive, comprehensible, and flexible. The logical encapsulation provided by page components can reduce the complexity of implementing and interacting with the associated application features. This is especially true for views that contain multiple instances of the same component.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Written with &lt;a href=&quot;https://stackedit.io/&quot;&gt;StackEdit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name>Scott Babcock</name><email>scbabco@rei.com</email></author><category term="mobile" /><category term="xcuitest" /><category term="mobile" /><category term="swift" /><summary type="html">Creating Stable, Maintainable User Interface Test Automation in Swift</summary></entry><entry><title type="html">XCUITest Automation: Page Object Models for iOS Test Automation</title><link href="https://engineering.rei.com/mobile/xcuitest-page-object-models.html" rel="alternate" type="text/html" title="XCUITest Automation: Page Object Models for iOS Test Automation" /><published>2024-01-26T00:00:00+00:00</published><updated>2024-01-26T00:00:00+00:00</updated><id>https://engineering.rei.com/mobile/xcuitest-page-object-models</id><content type="html" xml:base="https://engineering.rei.com/mobile/xcuitest-page-object-models.html">&lt;h2 id=&quot;creating-stable-maintainable-user-interface-test-automation-in-swift&quot;&gt;Creating Stable, Maintainable User Interface Test Automation in Swift&lt;/h2&gt;

&lt;p&gt;When creating test automation to validate the performance and behavior of a complex software application, adhering to solid engineering practices will produce stable, maintainable tests. This is especially true when driving the application through its user interface. Page Object Model (POM) design patterns provide a solid framework to achieve this objective.&lt;/p&gt;

&lt;h3 id=&quot;implementing-page-object-model-pom-patterns-in-swift&quot;&gt;Implementing Page Object Model (POM) patterns in Swift&lt;/h3&gt;

&lt;p&gt;Page Object Model (POM) design patterns, originally conceived to automate web application user interface tests, can be applied to testing iOS application through the XCUITest framework. Following a page-model approach produces structured interfaces that your tests can use to drive the target application and verify expected behaviors.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Each application main view is associated with a separate page model class.&lt;/li&gt;
  &lt;li&gt;Functions that trigger navigation wait for the expected landing view to appear and return an instance of the corresponding page model class.&lt;/li&gt;
  &lt;li&gt;To facilitate navigation rewinding, stackable page model classes are parameterized, with the type parameter supplying the return type for back/close operations.&lt;/li&gt;
  &lt;li&gt;Groups of elements that comprise distinct units of application functionality can be modeled as &lt;a href=&quot;xcuitest-page-components.html&quot;&gt;page component&lt;/a&gt; classes.&lt;/li&gt;
  &lt;li&gt;Repeated element groupings (e.g. - search result items) should be modeled as &lt;a href=&quot;xcuitest-page-components.html#search-context-for-page-component-collection-entries&quot;&gt;page component collections&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;xcuitest-element-locator-enumerations.html&quot;&gt;Define element locators in Swift enumerations&lt;/a&gt; to avoid duplication of element types and attributes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;synchronization---the-lynchpin-of-stable-automation&quot;&gt;Synchronization - The lynchpin of stable automation&lt;/h3&gt;

&lt;p&gt;In user interface automation, everything begins and ends with definite synchronization between the application under test (AUT) and the automation that drives it. This isn’t just a figure of speech; it’s literally true.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Before interacting with a view that’s just been opened, perform load-completion checks to ensure that the application is ready for action.&lt;/li&gt;
  &lt;li&gt;After performing an action that triggers an application state change (expanding a dropdown, opening a modal, etc.), wait for the expected response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adhering to these rules ensures that your automation interacts smoothly and reliably with the application. It also enables you to recognize unexpected behavior at the point where it’s encountered, failing with clear diagnostic output indicating what went wrong. Remember…&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;RULE #1: Fail Fast with Detailed Diagnostics&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;the-synchronizedview-protocol&quot;&gt;The SynchronizedView protocol&lt;/h4&gt;

&lt;p&gt;In the REI automation framework, all of our application view page models implement a &lt;strong&gt;SynchronizedView&lt;/strong&gt; protocol. The methods that facilitate page load synchronization are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;waitForView(required: Bool) -&amp;gt; Self?&lt;/code&gt;&lt;/strong&gt;
Wait for the view associated with this model to finish loading.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;checkViewCriteria() -&amp;gt; String?&lt;/code&gt;&lt;/strong&gt;
Determine if all evaluated criteria indicate that the view associated with this model has finished loading.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hasNetworkSpinner() -&amp;gt; Bool&lt;/code&gt;&lt;/strong&gt;
Determine if transition to the view associated with this model initially displays a network activity spinner.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;launchTheApplication() -&amp;gt; Self?&lt;/code&gt;&lt;/strong&gt;
Launch the target application and wait for the expected view to load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The foundation of the synchronization provided by the framework is the &lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;checkViewCriteria()&lt;/code&gt;&lt;/strong&gt; function. Each page class declares a view-specific implementation of this function. As described in the function header, the implementation returns &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; when all evaluated criteria are met. If any evaluated criterion is unmet, the function returns a string that describes the issue. If after twenty seconds the function doesn’t return &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt;, the affected test is terminated with the description of the unmet criterion.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The &lt;code class=&quot;highlighter-rouge&quot;&gt;checkViewCriteria()&lt;/code&gt; function should perform instantaneous evaluations; it should check for each defined criterion and return immediately upon encountering one that’s not met. The implementation of &lt;code class=&quot;highlighter-rouge&quot;&gt;waitForView()&lt;/code&gt; polls this function repeatedly until it returns &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; or the maximum delay interval has elapsed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5 id=&quot;page-object-model-protocol-part-1---synchronization&quot;&gt;Page Object Model Protocol (Part 1 - Synchronization)&lt;/h5&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// SynchronizedView.swift (Part 1)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Copyright © 2024 REI. All rights reserved.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;XCTest&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;SynchronizedView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// Determine if transition to the view associated with this model initially displays a network activity spinner.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: The default implementation of this function returns `false`; declare an override returning `true` if the associated view opens with a spinner.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: `true` if expecting an initial network spinner&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;hasNetworkSpinner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Determine if all evaluated criteria indicate that the view associated with this model has finished loading.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: `nil` if all evaluated criteria are satisfied; otherwise, description of unsatisfied view load criterion&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;checkViewCriteria&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Launch the target application and wait for the expected view to load.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: this view model; `nil` if expected view doesn't load&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;launchTheApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Terminate the target application.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;terminateTheApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Wait for the view associated with this model to finish loading.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: If ``hasNetworkSpinner()`` returns `true`, this function will wait for the network spinner to vanish prior to evaluation of view load criteria.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: If [required] is `true` and the standard load interval expires, the current test will fail with a message indicating this model and the description of the unsatified view load criterion.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Parameter required: [optional] `false` if incomplete loading of the associated view is allowed (default = `true`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: this view model; `nil` if view transition fails&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;waitForView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;extension&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;SynchronizedView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;waitForView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;hasNetworkSpinner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// TODO: application-dependent spinner check&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Utility&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;waitForExpression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;checkViewCriteria&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;standardTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;  &lt;span class=&quot;kd&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Utility&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failWithAlertCheck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed opening '&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getViewName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;' view: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;  &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;navigation---building-a-chain-of-synchronized-views&quot;&gt;Navigation - Building a chain of synchronized views&lt;/h3&gt;

&lt;p&gt;The navigation strategy employed by the REI automation framework explicitly defines the relationships between associated views:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Every method that triggers a view transition returns an instance of the page class for the expected landing view.&lt;/li&gt;
  &lt;li&gt;To facilitate retracing steps in the navigation stack, each page object retains the instance that spawned it (the origin).&lt;/li&gt;
  &lt;li&gt;Each navigation stack originates from a root view, which has no origin.&lt;/li&gt;
  &lt;li&gt;Non-root page classes are parameterized, and the parameter type specified when each instance is created resolves the class of the origin when stepping back down the stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first point of this strategy is the key to effortless stability at view transitions. When your model returns a new page object, the framework has already ensured that the corresponding view is present and prepared for the next action. This is accomplished in &lt;code class=&quot;highlighter-rouge&quot;&gt;openView()&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;returnToOrigin()&lt;/code&gt; via the &lt;code class=&quot;highlighter-rouge&quot;&gt;waitForView()&lt;/code&gt; function, which uses the &lt;code class=&quot;highlighter-rouge&quot;&gt;checkViewCriteria()&lt;/code&gt; function of the new page object for synchronization.&lt;/p&gt;

&lt;h5 id=&quot;page-object-model-protocol-part-2---object-chaining&quot;&gt;Page Object Model Protocol (Part 2 - Object Chaining)&lt;/h5&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// SynchronizedView.swift (Part 2)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Copyright © 2023 REI. All rights reserved.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;XCTest&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;protocol&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;SynchronizedView&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// Origin of this view.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: The origin is the view that the application returns to when the user taps **Back** or **Close**; may be `nil`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Target application for this view.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Required constructor:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Parameters&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - origin: [optional] Origin of this view (default = `nil`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - app: Target application for this instance&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``origin``&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Open the indicated view by tapping the specified element.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: If [required] is `true` and the standard load interval expires, the current test will fail with a message indicating this model and the description of the unsatified view load criterion.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Parameters:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - viewType: Class of the automation model associated with the view that opens after tapping the specified element&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - targetApp: [optional] Target application for view being opened (default = `self.APP`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - element: Element that triggers the expected view transition&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - fixed: [optional] `true` if the specified element is non-scrollable (default = `false`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - required: [optional] `false` if incomplete loading of the associated view is allowed (default = `true`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: target view model; `nil` if view transition fails&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``waitForView``&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;openView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;viewType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;targetApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byTapping&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                &lt;span class=&quot;nv&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Tap **Back** / **Close** to navigate back to the origin of the current view, as registered in this model.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: This function is unsupported for &quot;navogation hub&quot; models, as these represent the bottom of the nav stack and therefore have no related **Back** action.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Parameters:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - element: [optional] Element that triggers the expected view transition (default = `nil`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - fixed: [optional] `true` if the specified element is non-scrollable (default = `false`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - required: [optional] `false` if incomplete loading of the associated view is allowed (default = `true`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: origin view model; `nil` if view transition fails&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``origin``&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;returnToOrigin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byTapping&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;best-practices-for-stable-automation-models&quot;&gt;Best practices for stable automation models&lt;/h3&gt;

&lt;p&gt;The REI framework implements several patterns that promote stable, reliable operation of our user-interface test automation. The full benefits of these facilities rely on the proper implementation of protocol functions within the page classes.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use the &lt;code class=&quot;highlighter-rouge&quot;&gt;openView()&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;returnToOrigin()&lt;/code&gt; functions for all view transitions:
    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;openKnowledgeBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;KnowledgeBaseHome&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SettingsHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;openView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;KnowledgeBaseHome&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SettingsHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;byTapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;knowledge_base_link&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Perform explicit synchronization after each action that triggers a state transition (e.g. - switch filter mode):
    &lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;switchMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;via&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;reference&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isSelected&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;reference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;reference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isSelected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isSelected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;kt&quot;&gt;XCTFail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Filter mode failed to become selected&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Each of these examples ensures that the transition is complete and the application is in the expected state before returning to the caller. This practice, called &lt;strong&gt;&lt;em&gt;exit-state synchronization&lt;/em&gt;&lt;/strong&gt;, is far more efficient and reliable than adding synchronization at the beginning of down-stream functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;best-practices-for-test-implementation&quot;&gt;Best practices for test implementation&lt;/h3&gt;

&lt;p&gt;Adhering to a few core practices affords your tests the greatest benefit from the synchronization and stability provided by the REI framework:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Only the initial page object is instantiated directly by the tests, typically in the &lt;code class=&quot;highlighter-rouge&quot;&gt;setupWithError()&lt;/code&gt; function.&lt;/li&gt;
  &lt;li&gt;All other page objects are returned by functions that trigger view transitions.&lt;/li&gt;
  &lt;li&gt;Use the &lt;code class=&quot;highlighter-rouge&quot;&gt;guard let&lt;/code&gt; pattern to resolve the optional page object values returned by transition-triggering functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example test class shown below demonstrates these practices. Note that the test code doesn’t require explicit verification of landing views at transitions, because the model does this automatically.&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;import&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;XCTest&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;MSABasicNavigationTests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCTestCase&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ascentHomeView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AscentHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt;  &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;setUpWithError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;AscentHomeView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;testingApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// stop immediately on failure&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;continueAfterFailure&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt;  &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;tearDownWithError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;testNavigationToAscentKnowledgeBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt;  &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launchTheApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;settingsHomeView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;footerBar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;selectSettingsTab&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ascentKnowledgeBase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settingsHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;openKnowledgeBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt;  &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascentKnowledgeBase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backToOrigin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The preceding example demonstrates the page object chaining behavior of the framework. The output of each function that triggers a view transition is the page object for the view that was activated by the function. If a &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt; is returned, the test exits immediately. Because we set &lt;code class=&quot;highlighter-rouge&quot;&gt;continueAfterFailure&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;false&lt;/code&gt; during test setup, this is academic - an error has already been registered explaining the issue. However, it’s still a good practice.&lt;/p&gt;

&lt;h4 id=&quot;handling-transitions-with-interstitial-toast&quot;&gt;Handling transitions with interstitial “toast”&lt;/h4&gt;

&lt;p&gt;When an action triggers a view transition that also includes a “toast” message, special handling is required to capture and respond to the “toast” message and synchronize with the newly activated view. These events must be handled within the context of a single function; Attempting to handle the “toast” message separately is far too likely to result in unreliable synchronization. The REI framework provides two functions that encapsulate the handling of transitions with “toast”:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c1&quot;&gt;/// Open the indicated view by tapping the specified element, waiting for an expected &quot;toast&quot; message and performing the action it presents.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: If [required] is `true` and the standard load interval expires, the current test will fail with a message indicating this model and the description of the unsatified view load criterion.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Parameters:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - viewType: Class of the automation model associated with the view that opens after tapping the specified element&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - originType: [optional] Class of landing view model if back/close of target view performs unlinked navigation (default = `nil`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - element: Element that triggers the expected view transition&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - fixed: [optional] `true` if the specified element is non-scrollable (default = `false`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - required: [optional] `false` if incomplete loading of the associated view is allowed (default = `true`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: **TransitionWithToast()** object containing target view model and &quot;toast&quot; information; object with default values if view transition fails or &quot;toast&quot; isn't found&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``waitForView``&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``TransitionWithToast``&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``ToastInfo``&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;openViewWithToastAction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;viewType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;originType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;BaseView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byTapping&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                               &lt;span class=&quot;nv&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TransitionWithToast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;/// Tap **Back** / **Close** to navigate back to the origin of the current view, as registered in this model, waiting for an expected &quot;toast&quot; message and performing its action if specified.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// &amp;gt; Note: This function is unsupported for &quot;navigation hub&quot; view models, as these represent the bottom of the nav stack and therefore have no related **Back** action.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Parameters:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - element: Element that triggers the expected view transition&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - fixed: [optional] `true` if the specified element is non-scrollable (default = `false`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - required: [optional] `false` if incomplete loading of the associated view is allowed (default = `true`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - performAction: [optional] `true` if the action presented by the &quot;toast&quot; message should be performed (default = `false`)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - Returns: **TransitionWithToast()** object containing target view model and &quot;toast&quot; information; object with default values if view transition fails or &quot;toast&quot; isn't found&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``origin``&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``waitForView``&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``TransitionWithToast``&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;/// - See: ``ToastInfo``&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;returnToOriginWithToast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byTapping&lt;/span&gt;  &lt;span class=&quot;nv&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                  &lt;span class=&quot;nv&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;performAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TransitionWithToast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;toastinfo&quot;&gt;ToastInfo&lt;/h5&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;ToastInfo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hasAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Bool&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;transitionwithtoast&quot;&gt;TransitionWithToast&lt;/h5&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;struct&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;TransitionWithToast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;toastInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ToastInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;toastInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ToastInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toastInfo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;toastInfo&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As indicated, the preceding functions handle view transitions that include the appearance of interstitial “toast” messages. The return values of these functions (&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;TransitionWithToast&lt;/code&gt;&lt;/strong&gt;) encapsulate the view model and a “toast” information (&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ToastInfo&lt;/code&gt;&lt;/strong&gt;) object. These are the potential outcomes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If the expected view activates, a reference to the view model is returned in the &lt;em&gt;[view]&lt;/em&gt; property.&lt;/li&gt;
  &lt;li&gt;If the expected view doesn’t activate, the &lt;em&gt;[view]&lt;/em&gt; property will be set to &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt;.
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: If the &lt;em&gt;[required]&lt;/em&gt; argument of the function call is &lt;code class=&quot;highlighter-rouge&quot;&gt;true&lt;/code&gt; (the default value), a test failure will be registered.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;If a “toast” message appears, a &lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ToastInfo&lt;/code&gt;&lt;/strong&gt; object is returned in the &lt;em&gt;[toastInfo]&lt;/em&gt; property containing the message text, identifier, and action.&lt;/li&gt;
  &lt;li&gt;If no “toast” message appears, the &lt;em&gt;[toastInfo]&lt;/em&gt; property will be set to &lt;code class=&quot;highlighter-rouge&quot;&gt;nil&lt;/code&gt;.
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: No test failure is registered.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The test itself must determine whether the returned “toast” information (or lack thereof) meets expectations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following demonstrates the application of &lt;code class=&quot;highlighter-rouge&quot;&gt;openViewWithToastAction()&lt;/code&gt; to tap an element and perform the navigation provided by the “toast” message:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addLabelAndOpenPrintBasket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TransitionWithToast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;PrintBasketView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;optionsSheet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options_sheet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;APP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;selector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optionsSheet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;openViewWithToastAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;PrintBasketView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;originType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ToolsHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                       &lt;span class=&quot;nv&quot;&gt;byTapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that the “origin” type is specified here. This isn’t always required, but the origin of the Print Basket is the Tools Home view, not the “quick print” menu.  Here’s an example of a test that uses this function:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testPrintOptions_PrintBasket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sku&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2028570010&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launchTheApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;productDetailsView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ascentHomeView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;searchBar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;searchForSingleItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sku&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;quickPrintMenu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;productDetailsView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tapPrintingOptionsButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;transition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quickPrintMenu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addLabelAndOpenPrintBasket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;printBasketView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toastInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1 label added Print Basket&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Toast message mismatch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;printBasketView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getPrintBasketItemCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Print basket item count mismatch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;guard&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;printBasketView&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backToToolsHome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This test opens a product page by searching for a SKU, opens the “quick print” menu, adds a label, and opens the print basket via the “toast” action. Synchronization and verification are performed at every step.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Applying Page Object Model (POM) patterns to the implementation of XCUITest automation produces well-structured interfaces that are rational and maintainable. Ubiquitous definite synchronization between your tests and the target application ensure reliable, efficient execution. Utilization of framework-supported linked navigation with automatic page-load verification ensures that you always land where you expect - or fail immediately when you don’t. Following the “fail fast with detailed diagnostics” rule eliminates much of the ambiguity that can otherwise make the process of investigating test failures frustrating and time-consuming.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Written with &lt;a href=&quot;https://stackedit.io/&quot;&gt;StackEdit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name>Scott Babcock</name><email>scbabco@rei.com</email></author><category term="mobile" /><category term="xcuitest" /><category term="mobile" /><category term="swift" /><summary type="html">Creating Stable, Maintainable User Interface Test Automation in Swift</summary></entry><entry><title type="html">XCUITest Automation: Encapsulating Element Locators in Swift Enumerations</title><link href="https://engineering.rei.com/mobile/xcuitest-element-locator-enumerations.html" rel="alternate" type="text/html" title="XCUITest Automation: Encapsulating Element Locators in Swift Enumerations" /><published>2023-11-29T00:00:00+00:00</published><updated>2023-11-29T00:00:00+00:00</updated><id>https://engineering.rei.com/mobile/xcuitest-element-locator-enumerations</id><content type="html" xml:base="https://engineering.rei.com/mobile/xcuitest-element-locator-enumerations.html">&lt;h2 id=&quot;creating-stable-maintainable-user-interface-test-automation-in-swift&quot;&gt;Creating Stable, Maintainable User Interface Test Automation in Swift&lt;/h2&gt;

&lt;p&gt;When creating test automation to validate the performance and behavior of a complex software application, adhering to solid engineering practices will produce stable, maintainable tests. This is especially true when driving the application through its user interface.&lt;/p&gt;

&lt;p&gt;In user-interface automation, following a page-model approach provides a structured interface that your tests can use to drive the target application and verify expected behaviors. A foundational feature of any user-interface automation strategy is a mechanism to locate and interact with the elements of the interface - buttons, input fields, descriptive text, et cetera.&lt;/p&gt;

&lt;p&gt;Apple’s user-interface automation framework for driving iOS application is &lt;a href=&quot;https://developer.apple.com/documentation/xctest/user_interface_tests&quot;&gt;XCUITest&lt;/a&gt;. This framework provides a rich set of classes, properties, and enumerations from which to specify the characteristics of target elements. To produce cohesive models that encapsulate all the details of your element locators, consider Swift enumerations.&lt;/p&gt;

&lt;h3 id=&quot;defining-element-locators-in-swift-enumerations&quot;&gt;Defining element locators in Swift enumerations&lt;/h3&gt;

&lt;p&gt;Two common issues that complicate the usage and maintenance of any programming interface are duplicate definitions of constant values and incomplete declarations that require additional context to be used. Each of these characteristics presents challenges when the definitions of associated elements in the target application change, because you’re forced to track down all of the related definitions and apply the same revisions everywhere.&lt;/p&gt;

&lt;p&gt;To avoid these sorts of issues, declare element locators in a comprehensive Swift enumeration in the view or component class that’s responsible for interacting with the corresponding elements. For example:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_title&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;refine_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;facet_label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;filter_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;XCUIElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;view_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;navigationBars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;refine_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;collectionViews&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;facet_label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;nameOrKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;predicate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSPredicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;identifier == %@ AND (label == %@ OR value == %@)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                            &lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nameOrKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nameOrKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;matching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;predicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;element&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticTexts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;facetKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;filterName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;cellPredicate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSPredicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;identifier == %@ AND value == %@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                                                &lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;facetKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isEmpty&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;matching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cellPredicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;containing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;staticText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;FILTER_LABEL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;element&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;labelPredicate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSPredicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;identifier == %@ AND label BEGINSWITH %@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                                     &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;FILTER_LABEL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;matching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cellPredicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;containing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;labelPredicate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;element&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cells&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rawValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;view_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Search Filter&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;refine_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;facet_label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;RefineSearch.filterFacet.label&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;filter_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;RefineSearch.filterName.cell&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;FILTER_LABEL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;RefineSearch.filterName.label&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The preceding declaration defines  a &lt;code class=&quot;highlighter-rouge&quot;&gt;locator&lt;/code&gt; function, a &lt;code class=&quot;highlighter-rouge&quot;&gt;rawValue&lt;/code&gt; computed property, a nested &lt;strong&gt;Constants&lt;/strong&gt; enumeration, and four element locator values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A simple locator for an element with a constant identifier…
    &lt;ul&gt;
      &lt;li&gt;… produces a constant context-relative element locator.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;A parameterized locator with a single required associated value…
    &lt;ul&gt;
      &lt;li&gt;… produces context-relative element locators based on the specified value.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;A parameterized locator with a single optional associated value…
    &lt;ul&gt;
      &lt;li&gt;… [value omitted] produces a constant context-relative locator that finds multiple matching elements.
  … OR …&lt;/li&gt;
      &lt;li&gt;… [value defined] produces context-relative locators based on the defined value that find unique elements.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;A parameterized locator with a pair of optional associated values…
    &lt;ul&gt;
      &lt;li&gt;… [values omitted] produces a constant context-relative locator that finds multiple matching elements.
  … OR …&lt;/li&gt;
      &lt;li&gt;… [first defined, second empty] produces context-relative locators based on the defined value that finds multiple matching elements.
  … OR …&lt;/li&gt;
      &lt;li&gt;… [both defined] produces context-relative locators based on the defined values that find unique elements.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that the &lt;code class=&quot;highlighter-rouge&quot;&gt;locator&lt;/code&gt; function requires a “context” argument. This is the search context for the returned element locator. The element locator values produced by &lt;code class=&quot;highlighter-rouge&quot;&gt;locator&lt;/code&gt; acquire the identifiers they need from the &lt;code class=&quot;highlighter-rouge&quot;&gt;rawValue&lt;/code&gt; computed property. Associated values are declared or omitted in each context (&lt;code class=&quot;highlighter-rouge&quot;&gt;locator&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;rawValue&lt;/code&gt;) as needed.&lt;/p&gt;

&lt;p&gt;The “context” argument will typically be the “application” object, which will search the entire view hierarchy. To find elements within a sub-context, provide this instead:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;refinement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;refineView&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;refine_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;refinement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;XCUIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;filterElem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Element&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter_cell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;refineView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filterElem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isSelected&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filterElem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the preceding example, the “application” object is the context of the “refine” view locator, which defines the context for the “filter” element locator.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: For simple cases, localized handling of sub-contexts like the “refine” view does the trick. When modeling more complex sub-contexts with multiple associated elements and related behaviors, the use of component classes can provide cleaner, more maintainable designs. The details of this structure are documented &lt;a href=&quot;xcuitest-page-components.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The nested &lt;strong&gt;Constants&lt;/strong&gt; enumeration is used to define constant value that are used multiple time within the &lt;strong&gt;Element&lt;/strong&gt; enumeration, but are not associated with a defined element locator value. This “constants enumeration” pattern ensures that each value is defined only once, avoiding the risk of retaining stale copies when value updates are needed.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The encapsulation of element locators in comprehensive Swift enumerations can eliminate the confusion and inconsistencies that bedevil other approaches. By defining all aspects of each target element in a single place, the task of debugging and maintaining element locators is greatly simplified. With context scoping and associated values, each locator enumeration constants can provide selectors with graduated levels of specificity - from global all-of-type matching to context-constrained unique-instance matching.&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;h3 id=&quot;note-regarding-another-enum-based-locator-definition-strategy&quot;&gt;Note regarding another enum-based locator definition strategy&lt;/h3&gt;

  &lt;p&gt;While preparing to write this article, I performed a search to determine if others have described similar strategies. I found &lt;a href=&quot;https://medium.com/quality-engineering-university/xcuitests-best-practices-for-organizing-locators-with-swift-enumerations-452da45fe7d5&quot;&gt;this article&lt;/a&gt; by &lt;a href=&quot;https://medium.com/@nshthshah&quot;&gt;Nishith Shah&lt;/a&gt;, which begins with the same basic approach I used. As this strategy is developed through the article, a few key differences emerge:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;While the use of raw values solves the issue of duplicated constant declarations, this choice precludes the use of associated values which greatly enhance the expressiveness of locators for element collections. While these can be modeled via nested components, this level of factoring often results in a proliferation of tiny classes with limited functionality which are difficult to debug and maintain.&lt;br /&gt;
The &lt;code class=&quot;highlighter-rouge&quot;&gt;rawValue&lt;/code&gt; computed property demonstrated in the example above also avoids duplicated constant declarations, with the added benefit that the resulting identifiers can incorporate specified associated values.&lt;/li&gt;
    &lt;li&gt;Grouping elements by type and handling them in a generic fashion increases complexity and makes the code harder to maintain. The apparent repetition incurred by handling each case independently eliminates the risk of breaking cases you weren’t intending to change when updating cases that require change.&lt;/li&gt;
    &lt;li&gt;The use of a computed property instead of a function to generate locators precludes the option of providing a search context. While this context is often the application under test, it may also be the root element of a component model (e.g. - an item in a table view). It might also be an entirely different application, such as Safari or Springboard (which is used to access elements in the status bar).&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;Written with &lt;a href=&quot;https://stackedit.io/&quot;&gt;StackEdit&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name>Scott Babcock</name><email>scbabco@rei.com</email></author><category term="mobile" /><category term="xcuitest" /><category term="mobile" /><category term="swift" /><summary type="html">Creating Stable, Maintainable User Interface Test Automation in Swift</summary></entry><entry><title type="html">Switching Trails to SwiftUI: Our Journey at REI</title><link href="https://engineering.rei.com/mobile/swift-ui.html" rel="alternate" type="text/html" title="Switching Trails to SwiftUI: Our Journey at REI" /><published>2023-10-17T00:00:00+00:00</published><updated>2023-10-17T00:00:00+00:00</updated><id>https://engineering.rei.com/mobile/swift-ui</id><content type="html" xml:base="https://engineering.rei.com/mobile/swift-ui.html">&lt;h2 id=&quot;declarative-vs-imperative-programming&quot;&gt;Declarative vs. Imperative Programming&lt;/h2&gt;

&lt;p&gt;When providing instructions for packing a backpack in an imperative framework, you would list each of the &lt;a href=&quot;https://www.rei.com/learn/expert-advice/ten-essentials.html&quot;&gt;ten essentials&lt;/a&gt; and provide exact instructions on locating each item and fitting them all nicely within your pack. In a declarative framework however, you don’t need to meticulously outline these steps. You can instead define the result — a backpack packed with these ten items. The system then figures out how to obtain and fit them in the pack.&lt;/p&gt;

&lt;p&gt;This of course, requires you to trust the system to choose the right items and arrange them correctly.&lt;/p&gt;

&lt;p&gt;When it comes to computer programming, often there’s little doubt the system will do what you expect. Higher order functions like ‘map’, ‘reduce’ and ‘filter’ give us a taste of declarative programming. Rather than explicitly looping through an array and manipulating data, you can use these methods to declare what you want done and the system will perform the desired manipulation to each element.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/swift-ui/declaritive-vs-imparitive.png&quot; alt=&quot;An example of list manipulation in a declaritive way versus imperative.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The declaritive version of these simple list manipulations is half the length as the imperative version, and easier to follow.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When it comes to user interfaces however, there is much more ambiguity in the desired outcome. With that ambiguity comes a reluctance to relinquish control. Can you really trust the system to size things correctly on different form factors without explicit instructions? Will a long list scroll performantly without giving detailed instructions on how and when to create new views versus simply reusing old ones?&lt;/p&gt;

&lt;p&gt;Declarative UI frameworks say &lt;em&gt;yes&lt;/em&gt; and have been rising in popularity. In 2019 Apple put out their own declarative UI framework — SwiftUI.&lt;/p&gt;

&lt;h2 id=&quot;deciding-to-declare&quot;&gt;Deciding to Declare&lt;/h2&gt;

&lt;p&gt;Like most iOS developers, we approached SwiftUI with some trepidation here at REI. The framework is still evolving and unlike longer lived frameworks, the answers to questions that arise might not yet be chronicled on StackOverflow. The benefits however are clear. The concise, descriptive SwiftUI code makes maintaining, refactoring, and reusing components much simpler than it was before. It also seems clear that it’s Apple’s preferred framework, with components such as Home Screen Widgets requiring SwiftUI. Navigating the SwiftUI learning curve seems inevitable for iOS development, it’s only a question of when.&lt;/p&gt;

&lt;p&gt;With this in mind, we recently decided to move to SwiftUI for our retail employee app, Ascent. This was an easier decision for this app than most, as we have the luxury of owning the devices the app is deployed on. We can ensure everyone is on the latest iOS version, allowing us to use latest and greatest features of SwiftUI as they become available. The roadmap for the app also lended itself nicely to an overhaul. As we continued to add features to the app, it required a redesign of the fundamental navigation structure. This was the perfect opportunity to build a foundation of SwiftUI.&lt;/p&gt;

&lt;h2 id=&quot;navigating-the-move-to-swiftui&quot;&gt;Navigating the Move to SwiftUI&lt;/h2&gt;

&lt;p&gt;We planned for all new development to be in SwiftUI, and to refactor existing screens to SwiftUI when meaningful changes need to be made. To set ourselves up for success, we adopted a SwiftUI architecture that would make refactoring existing UIKit screens as painless as possible.&lt;/p&gt;

&lt;p&gt;Our UIKit architecture follows the &lt;a href=&quot;https://saad-eloulladi.medium.com/ios-swift-mvp-architecture-pattern-a2b0c2d310a3&quot;&gt;Model, View, Presenter.&lt;/a&gt; (MVP) pattern. These presenters contain the business logic that setup the data (model) needed to a particular screen (view), and then communicates the necessary updates to the view. The communication between presenter and view are backed by protocols, allowing mock views for easy unit testing.&lt;/p&gt;

&lt;p&gt;When refactoring an existing UIKit using MVP to SwiftUI, we were able to keep the presenters largely unchanged. The view however is replaced by a new SwiftUI view object, composed of one or many subviews. Each subview defines a ViewModel for anything that can change in it. Then, we can add these ViewModel objects as @Published properties to our presenters, and reference them as @State properties in the top level SwiftUI view. Instead of presenter invoking the protocol methods on the view, we simply update the viewModels to the desired state when appropriate. This ends up looking a lot like a &lt;a href=&quot;https://www.hackingwithswift.com/books/ios-swiftui/introducing-mvvm-into-your-swiftui-project&quot;&gt;Model, View, ViewModel&lt;/a&gt; architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/static/images/articles/swift-ui/AscentSection.png&quot;&gt;&lt;img src=&quot;/static/images/articles/swift-ui/AscentSectionSmall.png&quot; alt=&quot;Example of ViewModel and it's use in a Presenter&quot; /&gt; &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Our AscentSection component, its use in the ToolsPresenter as @Published properties, and resultant screen.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The result is very clean and modular view code that naturally lends itself to reusable components. We also retain testability for the business logic in our presenters. Instead of setting up mock views, we simply observe the @Published properties in each presenter and listen for the expected updates.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/swift-ui/unit-test.png&quot; alt=&quot;An example of unit testing a @Published property&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Developing in this environment can be a joy. Instead of being bogged down by boilerplate code and view lifecycles, one can focus on what really matters-— the look and feel of the app and the associated business logic. But unfortunately this isn’t always the case…&lt;/p&gt;

&lt;h2 id=&quot;bumps-on-the-trail&quot;&gt;Bumps on the Trail&lt;/h2&gt;

&lt;p&gt;While the end result is clean, modular views, getting there can sometimes be a struggle. Simple things, like setting padding around a view can have one scratching your head until you realize that the order of applying View Modifiers matters. When you add them to a view, you’re not simply setting properties on an object as in UIKit, but as the name implies, &lt;a href=&quot;https://www.swiftbysundell.com/questions/swiftui-modifier-order/&quot;&gt;you’re actually modifying the views&lt;/a&gt;. Luckily, despite SwiftUI being a newer framework, most issues like this are in fact Google-able, such as why a &lt;a href=&quot;https://stackoverflow.com/questions/57191013/swiftui-cant-tap-in-spacer-of-hstack&quot;&gt;row with a Spacer element isn’t registering tap events as you’d expect&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The real struggle occurs when SwiftUI just doesn’t want to do that which you declare. We faced one such frustrating issue trying to allow users to switch their keyboard type while typing. In UIKit, this was as simple as adding toolbar buttons to the keyboard and setting the keyboard type on the UITextField. SwiftUI however handles a similar setup differently. Instead of changing the keyboard type while the keyboard remains on the screen, SwiftUI sees fit to dismiss the keyboard once changes to the type are made, requiring another invocation of editing mode for the new type to appear. This wouldn’t be too big of a deal as we can programmatically invoke the keyboard again with @FocusState — except this does not work in our case where a &lt;a href=&quot;https://stackoverflow.com/questions/74245149/focusstate-textfield-not-working-within-toolbar-toolbaritem&quot;&gt;textfield is within a toolbar&lt;/a&gt; and you’re not at the top level of a NavigationStack.&lt;/p&gt;

&lt;p&gt;In situations like this it can be very hard to determine if the problem is you, or a limitation of SwiftUI. Others were reporting similar issues here, and we ended up taking the advice posted on Stack Overflow and the Apple Developer Forums… Use the imperative UITextField wrapped in a UIViewRepresentable to achieve what we had before. With our philosophy of “everything new in SwiftUI”, this seemed like a step backwards. We want to set our app up for the future. Adding new UIKit components seemed like taking on technical debt, but in the current state of SwiftUI you do need to make some compromises – either on functionality or technical decisions.&lt;/p&gt;

&lt;p&gt;Another challenge is performance. In the world of imperative programming, you can see where views are created and laid out, catching bottlenecks that may slow your app. Such details are abstracted away in declarative frameworks. This can lead to pitfalls where an update to a single textField can cause a cascading redraw of all views if you’re using a single view model for an entire screen as opposed to distinct components each with their own view model. In other cases, SwiftUI views don’t always do quite what you’d think they should. One could reasonably expect a SwiftUI List to act as a performant UITableView would, caching views and updating their content instead of constantly creating new views when scrolling. This however does not seem to be the case. When we used a List to display a large amount of search suggestions, we saw laggy non-performant scrolling. For &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/creating-performant-scrollable-stacks&quot;&gt;performant scrollable lists&lt;/a&gt;, Apple recommends using a LazyVStack inside of a Scrollview instead. This did work well for us, but it does not seem very intuitive and was not the first thought for fixing our issues.&lt;/p&gt;

&lt;h2 id=&quot;looking-back&quot;&gt;Looking Back&lt;/h2&gt;

&lt;p&gt;Our journey transitioning to SwiftUI was both rewarding and challenging. Over 70% of our Ascent’s views are now in SwiftUI. With a couple more releases we’ll get close to 100%. As components are built and knowledge in the framework is gained, the path ahead gets smoother—- both in developing new features and in maintaining and changing existing ones.&lt;/p&gt;

&lt;p&gt;Despite the challenges along the way, the journey has been a success. This is in no small part due to the support of the entire team. Our product owner saw the value in adopting the new technology and supported the journey even though it meant less accurate estimates and an occasional change to better accommodate what’s possible in SwiftUI. Our designer composed new features with a set of building blocks, lending themselves extremely well to modular, reusable SwiftUI views. Our testers put the app through the wringer, finding easily overlooked issues and updating UI tests to work with the new layouts. Our project manager kept us focused and moving in the right direction while our backend engineer ensured we didn’t have to struggle to get the data we need. And our architect gave us the nudge to go blaze this trail, giving backup when things got challenging.&lt;/p&gt;

&lt;p&gt;Looking back, we are now left with the concise, maintainable view code we were looking for. We’ve also gained vaulable experience in SwiftUI and declarative frameworks that will servce us well in the future. With Android and other platforms also adopting declarative frameworks, the specifics of each platform are becoming less and less important. Perhaps some day in the near future we won’t have to worry about platform dependant view code at all– we’ll be able to simply declare how we want the app to look across platforms.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/swift-ui/packing.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So, can you trust SwiftUI to pack your backpack? Yes, but make sure to double check the result and be willing to provide some imperative steps if the batteries on your flashlight need changing.&lt;/p&gt;</content><author><name>Ed Williams</name><email>edwilli@rei.com</email></author><category term="mobile" /><category term="mobile" /><category term="swiftUI" /><category term="declaritive" /><summary type="html">Declarative vs. Imperative Programming</summary></entry><entry><title type="html">Automated Accessibility Testing: From Passive to Preventative</title><link href="https://engineering.rei.com/frontend/automated-accessibility-testing.html" rel="alternate" type="text/html" title="Automated Accessibility Testing: From Passive to Preventative " /><published>2023-10-11T00:00:00+00:00</published><updated>2023-10-11T00:00:00+00:00</updated><id>https://engineering.rei.com/frontend/automated-accessibility-testing</id><content type="html" xml:base="https://engineering.rei.com/frontend/automated-accessibility-testing.html">&lt;p&gt;&lt;em&gt;Implementing and scaling automated accessibility testing on REI.com&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At REI, we believe that time outside is a fundamental right that all should be able to enjoy. This commitment to inclusion doesn’t start and end with marketing. It’s something we try and live up to every day as we design and engineer new customer features and experiences on REI.com.&lt;/p&gt;

&lt;p&gt;Back in 2021, like many other companies, we were stuck in an endless cycle of &lt;em&gt;audit &amp;amp; remediate&lt;/em&gt; with our digital accessibility efforts — reacting after-the-fact to the barriers we were putting in front of our customers. Recognizing a need to act sooner, we set out to engineer an automated solution that would be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Effective — limited in its false positives.&lt;/li&gt;
  &lt;li&gt;Scalable — easily translatable to any microsite environment within our common framework.&lt;/li&gt;
  &lt;li&gt;Passive — require minimal human intervention to sustain.&lt;/li&gt;
  &lt;li&gt;Blocking — not only advising on accessibility issues but preventing them from impacting people in the first place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now two years later, we’ve successfully built and scaled a suite of automated tests that are consumed by over 90% of our customer-facing applications, testing thousands of deployments per year for common accessibility missteps, and blocking inaccessible code from seeing the light of day.&lt;/p&gt;

&lt;p&gt;We know automation is only 30% of the accessibility testing puzzle, because not everything can be automated today, but we’re proud of the foundation we’ve built and the lessons we’ve learned along the way.&lt;/p&gt;

&lt;figure class=&quot;figcaption&quot;&gt;
  &lt;img class=&quot;figcaption__image&quot; src=&quot;/static/images/articles/automated-accessibility-testing/figure-1.png&quot; alt=&quot;screen capture of Jenkins dashboard graph, described in caption&quot; /&gt;
  &lt;figcaption class=&quot;figcaption__caption&quot;&gt;
    &lt;p&gt;&lt;em&gt;Figure 1: Accessibility Test Count - April '22 to Today&lt;/em&gt;&lt;/p&gt;
    &lt;p&gt;The graph above shows the benefits of integrating accessibility tests into a common framework, instead of relying on a team-by-team approach — rapid adoption. In October 2022, we were running less than 20 tests per day. Today, we're consistently over 300 with a greater than 80% pass rate.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;building-for-scale&quot;&gt;Building for Scale&lt;/h2&gt;

&lt;p&gt;From the beginning, our most important goal was enabling scalability. In talking to individual employees, we learned that accessibility testing often feels intrusive and additive to the day-to-day work of product teams. We wanted a solution that could be a seamless part of the product development process. Our test suite works by minimizing friction and maximizing positive outcomes for both employees and our customers.&lt;/p&gt;

&lt;p&gt;Another reason that scalability was important is due to the rate of change happening on REI.com. With the &lt;a href=&quot;https://engineering.rei.com/devops/how-we-built-a-microservices-platform.html&quot;&gt;decomposition of our code monolith&lt;/a&gt; into individual microsites and services underway, and due to the nature of our continuous delivery model, new applications are popping up all the time. It’s hard for our small accessibility team to keep up with the maintenance work.&lt;/p&gt;

&lt;p&gt;Luckily, many of the customer-facing web applications are built on our in-house framework, Crampon. A common framework allows us to take advantage of pre-existing templating, baking in accessibility best practices and testing infrastructure from day one. It also means that as we started our automated testing journey in 2021, our existing web applications were backwards compatible with our new ideas.&lt;/p&gt;

&lt;p&gt;To put in perspective just how easy it is now for developers to implement basic accessibility testing in their application today, I’ve included the steps below for a new or existing Crampon application:&lt;/p&gt;

&lt;p&gt;For new applications:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A new application is created via Chairlift, our internal application provisioning service.&lt;/li&gt;
  &lt;li&gt;The developer gives the application a name and selects &lt;em&gt;Crampon Application&lt;/em&gt; to create a Crampon-based microsite or microservice.&lt;/li&gt;
  &lt;li&gt;The developer chooses &lt;em&gt;Includes microsite UI&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;After completing the remaining steps in the set-up wizard, the developer now has a repository with our starter application that includes the latest version of Crampon, which includes an accessibility testing starter template by default.&lt;/li&gt;
  &lt;li&gt;All that’s left to do is specify the URLs to test and implement the &lt;code class=&quot;highlighter-rouge&quot;&gt;isCoOpA11yCompliant&lt;/code&gt; method into their existing Selenium-based system tests. These can be written to support tests for basic static content or more advanced applications can include dynamic page states and conditions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For existing applications:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A new change to the test suite has been introduced in the Crampon repository.&lt;/li&gt;
  &lt;li&gt;A new version of Crampon has been released.&lt;/li&gt;
  &lt;li&gt;Existing microsites upgrade their applications to the newest version of Crampon.&lt;/li&gt;
  &lt;li&gt;All that is left to do is specify the URLs to test and implement the &lt;code class=&quot;highlighter-rouge&quot;&gt;isCoOpA11yCompliant&lt;/code&gt; method into their existing Selenium-based system tests. These can be written to support tests for basic static content or more advanced applications can include dynamic page states and conditions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, we have taken much of the work of understanding accessibility and test libraries out of the equation and let test engineers do what they do best, write tests.&lt;/p&gt;

&lt;h2 id=&quot;ensuring-effectiveness&quot;&gt;Ensuring effectiveness&lt;/h2&gt;

&lt;p&gt;With scale comes challenges related to the effectiveness of our test suite. A one-size-fits-all solution isn’t tremendously effective, especially with 22 unique applications serving customer functions from landing page to checkout. Not to mention how diverse our product teams are in their approaches to writing acceptance criteria, code, and tests.&lt;/p&gt;

&lt;p&gt;Our first step was to decide on a base test library that could limit false positives while remaining flexible enough to conform to each unique application in a way that was seamless and performant. After extensive testing, we landed on &lt;a href=&quot;https://github.com/dequelabs/axe-core&quot;&gt;axe-core&lt;/a&gt;. It’s free, open source, something we were familiar with already, and allowed us to add/remove rules and standards easily.&lt;/p&gt;

&lt;p&gt;Then, after careful consideration, we narrowed down our current ruleset to just 42 rules. We intentionally exclude page elements that are included as part of our base template to eliminate test noise and ensure our suite is performant.&lt;/p&gt;

&lt;p&gt;With a solid ruleset in place, our next move was to exclude the header and footer navigation for testing and only test content within the core page content for each application. Our navigation is persistent across many pages, and we found that choosing to test this code once in isolation, compared to testing it within every application removed a lot of noise caused by test failures, allowing teams to work quicker.&lt;/p&gt;

&lt;p&gt;Accessibility tests run against our navigation (header and footer), as well. Our Header/Footer exists as its own microsite, which makes testing it easier.&lt;/p&gt;

&lt;p&gt;Finally, we needed to make it easy to make our tests blocking for deployments. We want to prevent issues from impacting customers. To do that in a way that’s durable, we needed to ensure tests are always passing, by accounting for the code we don’t own — 3rd party vendor content. Without doing this, 3rd parties could release inaccessible code and prevent our developers from pushing to production.&lt;/p&gt;

&lt;p&gt;We solved this problem by developing and implementing a change to our test suite via Crampon that enabled developers to skip testing for certain selectors within their UI.&lt;/p&gt;

&lt;p&gt;We rolled this out globally by working with individual teams to upgrade to the most recent version of our common framework. And with our implementation in place, teams are easily and efficiently able to understand how well their application does at passing automated accessibility tests, without the noise that navigation and vendor solutions might add.&lt;/p&gt;

&lt;h2 id=&quot;ushering-in-preventative-measures&quot;&gt;Ushering in preventative measures&lt;/h2&gt;

&lt;p&gt;With accessibility tests running on every microsite and in many cases passing, we could have stopped there. However, we were determined to do more than tell people their code had accessibility issues.&lt;/p&gt;

&lt;p&gt;We wanted to prevent the negative impacts of inaccessible code before it became reality.&lt;/p&gt;

&lt;p&gt;Initially our test suite was running in production only. In theory, we could have flipped the Boolean value from false to true and prevented people from shipping code that didn’t pass the test. This would have been a bit heavy-handed and potentially added additional friction when folks were on a tight deadline or in a code-freeze situation.&lt;/p&gt;

&lt;p&gt;Instead, we chose to implement an identical set of tests in each team’s staging environment. This acts as a proxy for blocking tests with less risk to deployments since teams test their work in staging before pushing to production. So far, this has been effective in preventing regression without negatively impacting our teams’ workflows.&lt;/p&gt;

&lt;h2 id=&quot;road-to-maturity&quot;&gt;Road to maturity&lt;/h2&gt;

&lt;p&gt;Now you’re wondering, &lt;em&gt;how did you scale so quickly?&lt;/em&gt; and much of that success can be attributed to our maturity plan, illustrated in the graphic below by our Principal Accessibility Engineer, &lt;a href=&quot;https://engineering.rei.com/authors.html#Harmony%20Hames&quot;&gt;Harmony Hames&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: For the purpose of this article you can think of staging environment and try pipelines interchangeably. This is pre-production code.&lt;/p&gt;

&lt;figure class=&quot;figcaption&quot;&gt;
  &lt;img class=&quot;figcaption__image&quot; src=&quot;/static/images/articles/automated-accessibility-testing/figure-2.png&quot; alt=&quot;screen capture of flow chart, described in text below&quot; /&gt;
  &lt;figcaption class=&quot;figcaption__caption&quot;&gt;
    &lt;p&gt;&lt;em&gt;Figure 2 - Illustrating how REI.com applications implemented our test suite&lt;/em&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Getting teams to prioritize accessibility testing wasn’t easy, and it wasn’t an overnight success. The choices we made and the features (like &lt;em&gt;exclude&lt;/em&gt; functionality) we developed were the result of a lot of trial and error. It took us almost 2 years to get to the stage we’re at now. That said, we chose to ease teams into change vs. forcing a lot at once as illustrated above:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Upgrade to the latest version of our framework, Crampon&lt;/li&gt;
  &lt;li&gt;Ensure you’re using the latest test method.&lt;/li&gt;
  &lt;li&gt;Get your production tests configured correctly if they’re not.&lt;/li&gt;
  &lt;li&gt;Get your production tests passing.&lt;/li&gt;
  &lt;li&gt;Implement tests in your staging environment to prevent regression.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At any given time, you’d have 2 teams in stage one, another few with passing tests, and a handful of others not on the latest version of Crampon. It felt like chaos but having a well-documented plan for a team to mature their practice and only 1-2 things to focus on at a time, provided just the right amount of motivation for teams to achieve what looked impossible.&lt;/p&gt;

&lt;h2 id=&quot;finding-and-closing-the-gaps&quot;&gt;Finding and closing the gaps&lt;/h2&gt;

&lt;p&gt;The tests are running on most user experiences, passing, and they’re blocking. We’re done, right? Not quite.&lt;/p&gt;

&lt;p&gt;Though the reality of the situation was that we’re running over 400 tests, split between staging and production, and most of REI.com is templated, we found that they were only as good as the templates we were covering. There were page types we weren’t covering with any accessibility tests.&lt;/p&gt;

&lt;p&gt;To solve this problem, we needed to understand where the gaps were, and then improve each application’s tests to achieve optimal coverage. To do that, we needed to drastically expand the scope of the tests, since today our problems were outside of our existing URL set.&lt;/p&gt;

&lt;p&gt;One, albeit cumbersome, way to determine which page types weren’t being covered, would be to browse them like a user would and run a browser extension (in our case axe DevTools, since it’s an identical test library to our pipeline tests). If there were test failures, it meant it contained failures due to rules we’d skipped, or it wasn’t tested.&lt;/p&gt;

&lt;p&gt;Based on a set of 2,600 pages which we believed would cover all page types, we estimated this route would take an estimated 86.6 hours to complete (2 min x 2,600 pages / 60 min). This is way too long, and way too cost prohibitive, especially if we wanted a solution that would keep up with our site’s pace of change.&lt;/p&gt;

&lt;h2 id=&quot;arborist&quot;&gt;Arborist&lt;/h2&gt;

&lt;p&gt;Enter Arborist, our homegrown automated solution to doing the work described above. This key piece of technology unlocked several benefits for us as we reached for the best possible test coverage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Increased visibility (9000% larger scope)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;3rd party audits of REI.com are especially limited in scope due to cost.&lt;/li&gt;
  &lt;li&gt;Our pipelines are testing an estimated 400 pages.&lt;/li&gt;
  &lt;li&gt;Arborist tests around 2,600 pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Increased efficiency (-94% reduction in time to results)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A human triggering the automated tests via a browser extension (axe DevTools, Google Lighthouse, etc.) would have taken an estimated 86.6 hours (2 min x 2,600 / 60 min) to complete a run of 2,600 pages.&lt;/li&gt;
  &lt;li&gt;Arborist runs for an average of only 4 hours and 32 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Increased frequency&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A human might test pages only as time and bandwidth allows.&lt;/li&gt;
  &lt;li&gt;Arborist runs on REI.com every Friday.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Increased flexibility&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Test libraries, people, and standards change. With an extra tool that runs outside of our deployment pipelines, we can now test new enhancements and rule sets before impacting product teams with global changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we’ve launched Arborist, we’ve been able to dial in our test coverage and improve upon the work we’d already worked with teams and applications to adopt. Additionally, we’re able to get more data, more often, something that’s been a challenge in the accessibility space.&lt;/p&gt;

&lt;figure class=&quot;figcaption&quot;&gt;
  &lt;img class=&quot;figcaption__image&quot; src=&quot;/static/images/articles/automated-accessibility-testing/figure-3.png&quot; alt=&quot;screen capture of flow chart, described in text below&quot; /&gt;
  &lt;figcaption class=&quot;figcaption__caption&quot;&gt;
    &lt;p&gt;&lt;em&gt;Figure 3 Each run of Arborist has shown a notable decline in automated accessibility issues, indicating we're not regressing, and highlighting what's remaining due to 3rd party vendors.&lt;/em&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Though we still have a lot of work to do, knowing that automated accessibility testing can only really prevent 30-50% of issues, and that manual testing is required, too, we’re proud of the testing infrastructure we’ve built. We hope to close the remaining gaps with the manual testing standards we’re piloting now.&lt;/p&gt;

&lt;p&gt;What was once a culture of annual audits and remediation, is now one that regularly checks in on whether their accessibility tests are passing. We’re actively preventing accessibility issues from reaching customers, leading to a more accessible and friendly shopping experience.&lt;/p&gt;

&lt;p&gt;Putting people over profit, so all people can enjoy time outside.&lt;/p&gt;</content><author><name>Cooper Hollmaier</name><email>chollma@rei.com</email></author><category term="frontend" /><category term="frontend" /><category term="accessibility" /><summary type="html">Implementing and scaling automated accessibility testing on REI.com</summary></entry><entry><title type="html">Introduction to Chaos Engineering</title><link href="https://engineering.rei.com/devops/chaos-engineering.html" rel="alternate" type="text/html" title="Introduction to Chaos Engineering" /><published>2023-06-14T00:00:00+00:00</published><updated>2023-06-14T00:00:00+00:00</updated><id>https://engineering.rei.com/devops/chaos-engineering</id><content type="html" xml:base="https://engineering.rei.com/devops/chaos-engineering.html">&lt;p&gt;&lt;em&gt;Does the thought of being oncall for a critical service keep you up at night?  (Or do bears?)&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;customer-scenario&quot;&gt;Customer scenario&lt;/h2&gt;

&lt;p&gt;Let’s say you had a great day walking around the zoo. Say hello to &lt;a href=&quot;https://www.nwtrek.org/animals/bears/grizzly-cubs/&quot;&gt;Huckleberry&lt;/a&gt;.  Or is it Hawthorne?  Does it matter? Look at those claws!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/chaos-engineering/grizzly.jpg&quot; alt=&quot;An image of a standing grizzly bear&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The thought of meeting either one in the wild keeps you up all night, so you decide to do some shopping.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/chaos-engineering/shoppingcart1.png&quot; alt=&quot;REI.com shopping cart with bear spray and bear can&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Clicking submit order and ready to go back to bed. Wait a minute - didn’t go as planned?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/chaos-engineering/shoppingcart2.png&quot; alt=&quot;Shopping failed to place order&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;engineer-scenario&quot;&gt;Engineer scenario&lt;/h2&gt;
&lt;p&gt;You’re the oncall engineer supporting REI’s shopping cart &amp;amp; checkout and you get an alert in the middle of the night that some service (xxxService) is throwing errors. After dialing into the major incident conference call you are asked what is the customer experience when xxxService is unavailable? Panic time, right? Good thing the camera is off!&lt;/p&gt;

&lt;p&gt;REI’s Checkout microservice orchestrates numerous dependencies to provide shopping cart and checkout functions for rei.com and mobile apps. For example, Customer Info, Dividend, Gift Card, Credit Card, Coupons, etc. With so many dependent services, it’s inevitable that something will break at some point. The goal is to minimize the customer impact during checkout.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Purposefully fuzzy relationship image&lt;/em&gt;
&lt;img src=&quot;/static/images/articles/chaos-engineering/relations.png&quot; alt=&quot;Purposefully fuzzy relationship image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;chaos-engineering-to-the-rescue&quot;&gt;Chaos Engineering to the rescue?&lt;/h2&gt;
&lt;p&gt;Chaos testing is just one of the many types of testing we do at REI to ensure our services are working properly.&lt;/p&gt;

&lt;p&gt;From &lt;a href=&quot;https://principlesofchaos.org/&quot;&gt;Principles of Chaos Engineering&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Chaos Engineering is the discipline of experimenting on a system in order to build confidence in the system’s capability to withstand turbulent conditions in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are many types of tests that you can do that could fall under the umbrella of Chaos Engineering.  For the purposes of this article, I have focused on testing individual services going down.&lt;/p&gt;

&lt;h3 id=&quot;game-day-scenario&quot;&gt;Game day scenario&lt;/h3&gt;
&lt;p&gt;This works best with the whole team present: Developers (frontend &amp;amp; backend), architects, SDETs, product managers, etc.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Hypothesis - What happens when the xxxService is unavailable?&lt;/li&gt;
  &lt;li&gt;Test - Disable xxxService. Since all of our services are invoked using the &lt;a href=&quot;https://resilience4j.readme.io/docs&quot;&gt;Resilience4j&lt;/a&gt; circuit breaker, this is achieved by putting the breaker into “Open” state. We did this in our test environment. You could also change the read timeout to 1ms, or use Wiremock or something similar to respond with a 5xx error. Side note - we’ve migrated from Hystrix to Resilience4j. See excellent article from Jeremiah Pierucci &lt;a href=&quot;https://engineering.rei.com/site-reliability/hystrix-circuit-breakers.html&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Observe - Did it behave the way you expected? While going through the exercise, sometimes the hypothesis did not match what we observed, and we needed to make some change to address it. In other cases, the website did not behave the way we wanted it to. Doing this live with the whole team was very enlightening for me, as we sometimes had different ideas of how the site should behave (For example, what happens when xxxService is not available - should it block customers from submitting their order? What should the user experience be?)&lt;/li&gt;
  &lt;li&gt;Fallback - do we want to add a fallback response when any of the services are down?&lt;/li&gt;
  &lt;li&gt;Document - With so many dependencies it is important to document the expected behavior (otherwise I won’t remember at 2 am when I am oncall)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;continuing-the-chaos-journey&quot;&gt;Continuing the Chaos journey&lt;/h2&gt;
&lt;p&gt;This testing is not intended to be done one time only - you will need to be diligent when new services are added, and it would make sense to repeat these tests on a semi-regular basis, as other parts of the code could have changed.&lt;/p&gt;

&lt;p&gt;The experiment described above can be thought of as a building block of Chaos Engineering.  As you gain confidence in this testing, you could increase scope to include things like database, cloud infrastructure, spikes in traffic, or really anything you can think of that could disrupt your service.  Eventually, as your Chaos Engineering practice matures, you could automate these tests and add them to your build pipeline, and perhaps eventually test in production.&lt;/p&gt;

&lt;p&gt;Hopefully, the result is that both customers and oncall engineers sleep well at night.  You do have robust monitoring and alerting for your application, right?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/chaos-engineering/success.png&quot; alt=&quot;Order successfully placed&quot; /&gt;&lt;/p&gt;</content><author><name>Tom Girard</name><email>tgirard@rei.com</email></author><category term="devops" /><category term="devops" /><summary type="html">Does the thought of being oncall for a critical service keep you up at night? (Or do bears?)</summary></entry><entry><title type="html">Accessibility + content design: a powerful combo to make you a better developer</title><link href="https://engineering.rei.com/frontend/accessibility-plus-content-design.html" rel="alternate" type="text/html" title="Accessibility + content design: a powerful combo to make you a better developer" /><published>2023-03-06T00:00:00+00:00</published><updated>2023-03-06T00:00:00+00:00</updated><id>https://engineering.rei.com/frontend/accessibility-plus-content-design</id><content type="html" xml:base="https://engineering.rei.com/frontend/accessibility-plus-content-design.html">&lt;p&gt;&lt;em&gt;A silly story that helps illustrate the benefits of understanding both accessibility and content design.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;If you didn’t learn about accessibility when you were learning development, you’re not alone. Even today it’s rare to find a development course offering more than a passing mention.&lt;/p&gt;

&lt;p&gt;Writing accessible code, at its core, means writing code correctly. When combined with accessible &lt;a href=&quot;https://contentdesign.london/blog/what-is-content-design&quot;&gt;content design&lt;/a&gt;, it will make your site generally more usable.&lt;/p&gt;

&lt;p&gt;But more importantly, it enables people with disabilities to experience, and use the internet. In the world we live in today, that is unquestionably a human right.&lt;/p&gt;

&lt;p&gt;Learning accessibility is no small task. There is much to learn, but it can be broken up into phases.  Many certification courses will provide study outlines, or you can create your own. No matter how you choose to learn, it is well worth the effort.&lt;/p&gt;

&lt;p&gt;Here are just a few of the basics:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You’ll learn about &lt;a href=&quot;https://www.w3.org/TR/WCAG21/&quot;&gt;WCAG (Web Content Accessibility Guidelines)&lt;/a&gt;&lt;/strong&gt;, which will give you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A framework to help you understand the basic concepts of accessible web content.&lt;/li&gt;
  &lt;li&gt;Success Criteria that provide techniques and a framework for testing your work.&lt;/li&gt;
  &lt;li&gt;A deeper understanding of how an inaccessible web impacts people with disabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You’ll learn about &lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.1/&quot;&gt;ARIA (Accessible Rich Internet Applications)&lt;/a&gt;&lt;/strong&gt;, which has:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;A library of existing patterns for web components in the &lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/&quot;&gt;ARIA Authoring Practices Guide (APG)&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;The expected keyboard patterns for these components.&lt;/li&gt;
  &lt;li&gt;Best practices around designing keyboard patterns for novel components.&lt;/li&gt;
  &lt;li&gt;Roles, states, and properties of elements on the web.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You will dive deeper into &lt;a href=&quot;https://web.dev/learn/html/semantic-html/&quot;&gt;semantic HTML&lt;/a&gt;&lt;/strong&gt;, and understand why it is critical for those using assistive technologies.&lt;/p&gt;

&lt;p&gt;Some examples of how this knowledge will benefit you as a developer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Your perspective will be more inclusive.&lt;/strong&gt; Not all users use a mouse. Not all users can see the screen. You’ll move away from this “default thinking.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Your code will be more performant.&lt;/strong&gt; Semantic and well-structured code requires less scripting and fewer DOM nodes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Your code will be SEO-friendly.&lt;/strong&gt; Using semantic elements and avoiding duplication of content is good for SEO rankings.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You’re less likely to create device-specific bugs.&lt;/strong&gt; Alternative input methods will be top-of-mind. Using built-in browser functionality is more reliable than custom coding.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You will be more confident.&lt;/strong&gt; You’ll learn to quickly break down designs into known components and patterns.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You will be more efficient.&lt;/strong&gt; You’ll see design issues in refinement, instead of bending over backward to fix them in code.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You will be more employable.&lt;/strong&gt; This &lt;a href=&quot;https://www.mdpi.com/2227-9709/7/1/8&quot;&gt;case study&lt;/a&gt; notes that “A study reported that web designers with competence in solving accessibility problems are more employable.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-content-design-thinking-and-accessibility-work-together&quot;&gt;How content design thinking and accessibility work together&lt;/h2&gt;

&lt;p&gt;Content design is about determining the best way to present the info people need to get stuff done—before getting into detailed interface design. Once the content and story interactions are clear, the design should center around that story flow and reinforce it.&lt;/p&gt;

&lt;p&gt;Developers can play a role in this by helping to preserve the meaning of content when they see it getting lost in design translation. It can be a strong habit to look at a visual design and immediately start converting it into a layout. But by seeing a design first as a content outline, it becomes easier to recognize potential issues.&lt;/p&gt;

&lt;p&gt;Many of the skills you gain learning accessibility support this kind of thinking. We’ll use an example to demonstrate this in action.&lt;/p&gt;

&lt;p&gt;Starting with a visual design, we’ll examine how the average developer might start to break this down conceptually.&lt;/p&gt;

&lt;h2 id=&quot;a-story-of-space-creatures&quot;&gt;A story of space creatures&lt;/h2&gt;

&lt;p&gt;For this mental exercise, we’ll put together a fake team:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Ted:&lt;/strong&gt; The designer. He’s new on the job, but he’s doing his best.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Chadwick:&lt;/strong&gt; a traditionally trained front-end developer.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Allie:&lt;/strong&gt; a front-end developer who trained in accessibility best practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll pretend this design contains actual content and not Forcem Ipsum.&lt;/p&gt;

&lt;p&gt;This is how the design came in from Ted.&lt;/p&gt;

&lt;figure class=&quot;figcaption&quot;&gt;
  &lt;img class=&quot;figcaption__image&quot; style=&quot;max-width: 250px; border: none; border-radius: 0;&quot; src=&quot;/static/images/articles/accessibility-plus-content-design/designer-spec_mobile.png&quot; alt=&quot;screen capture of the mobile version of the site, described in caption&quot; /&gt;
  &lt;figcaption class=&quot;figcaption__caption&quot;&gt;
    &lt;p&gt;&lt;strong&gt;Shown above:&lt;/strong&gt; The mobile version of the layout,
    showing the content in understandable reading order. The full text of the layout is below, but it is not necessary to read it to understand this article (maybe just to get a laugh).&lt;/p&gt;
    &lt;details class=&quot;expander&quot;&gt;
      &lt;summary class=&quot;expander__button&quot;&gt;Show full image text&lt;/summary&gt;
      &lt;div class=&quot;expander__text&quot;&gt;
        &lt;p&gt;Note: the heading levels are given here based on appearance (size).&lt;/p&gt;
        &lt;p&gt;Image of a child (will be described below, no spoilers!)&lt;/p&gt;
        &lt;p&gt;Heading level 1: Ewoks and ET: A Risky Mix&lt;/p&gt;
        &lt;p&gt;The approach will not be easy. You are required to maneuver straight down this trench and skim the surface to this point.&lt;/p&gt;
        &lt;p&gt;The shaft leads directly to the reactor system. A precise hit will start a chain reaction which should destroy the station. Only a precise hit will set up a chain reaction. The shaft is ray-shielded, so you&amp;rsquo;ll have to use proton torpedoes.&lt;/p&gt;
        &lt;p&gt;May the Force be with you!&lt;/p&gt;
        &lt;p&gt;Heading level 2: Speeder bikes AND flying bicycles?&lt;/p&gt;
        &lt;p&gt;At least the information in Artoo is still intact. What's so important? What's he carrying? The technical readouts of that battle station. I only hope that when the data is analyzed, a weakness can be found. It's not over yet! It is for me, sister!&lt;/p&gt;
        &lt;p&gt;She lied! She lied to us! I told you she would never consciously betray the Rebellion. Terminate her...immediately! Stand by, Chewie, here we go. Cut in the sublight engines. What the...? Aw, we've come out of hyperspace into a meteor shower.&lt;/p&gt;
        &lt;p&gt;Heading level 3: Speeder bike technology&lt;/p&gt;
        &lt;p&gt;You're sure the homing beacon is secure aboard their ship? I'm taking an awful risk, Vader. This had better work. Not a bad bit of rescuing, huh? You know, sometimes I even amaze myself. That doesn't sound too hard. Besides, they let us go. It's the only explanation for the ease of our escape. Easy...you call that easy? Their tracking us! Not this ship, sister.&lt;/p&gt;
        &lt;p&gt;Image of a wookie (again, description below, no spoilers).&lt;/p&gt;
        &lt;p&gt;Heading level 2: What about Wookiees?&lt;/p&gt;
        &lt;p&gt;They'll have that shield down on time.&lt;/p&gt;
        &lt;p&gt;Earned it, I have. Master Yoda, you can't die. Strong am I with the Force. but not that strong! Twilight is upon me and soon night must fall.&lt;/p&gt;
        &lt;p&gt;He will come to you and then you will bring him before me. As you wish. Luke! Luke! Oh, Master Luke. I'm afraid that Artoo's sensors can find no trace of Princess Leia.&lt;/p&gt;
        &lt;p&gt;Wait, Leia! Quick! Jam their comlink. Center switch! Hey, wait! Ahhh! Move closer! Get alongside that one! Get him! Keep on that one! I'll take these two!&lt;/p&gt;
      &lt;/div&gt;
    &lt;/details&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&quot;figcaption&quot;&gt;
  &lt;img class=&quot;figcaption__image&quot; style=&quot;border: none; border-radius: 0;&quot; src=&quot;/static/images/articles/accessibility-plus-content-design/designer-spec_desktop.png&quot; alt=&quot;screen capture of the desktop version of the site, described in caption&quot; /&gt;
  &lt;figcaption class=&quot;figcaption__caption&quot;&gt;
    &lt;p&gt;&lt;strong&gt;Shown above:&lt;/strong&gt; The desktop version of the layout.&lt;/p&gt;
    &lt;p&gt;In this version, the content appears to be in two columns, and
    the order has changed. Now &quot;What about Wookiees?&quot; comes right after the opening section, and the image of the Wookie has moved to the end of the content.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When the team meets to review the design before development, Ted asks if there is anything that needs clarification.&lt;/p&gt;

&lt;h3 id=&quot;chadwicks-review&quot;&gt;Chadwick’s review&lt;/h3&gt;

&lt;p&gt;Chadwick thinks about the visual design and how it compares between desktop and mobile. He looks at which elements shift, and thinks about how to do this with CSS. He notes a few things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It’s clearly a column design on desktop.&lt;/li&gt;
  &lt;li&gt;The elements are in a different order between desktop and mobile. He thinks through ways to handle that:
    &lt;ul&gt;
      &lt;li&gt;CSS Grid? Maybe. He could rearrange the content with grid-template-areas. But with the column design, he would likely have gaps as the page resized. Any answer to that is likely to be quite fussy, but possible.&lt;/li&gt;
      &lt;li&gt;He could duplicate the content and use CSS to show/hide the areas at different breakpoints.&lt;/li&gt;
      &lt;li&gt;He could use a buffered resize listener to move the content on page resize.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Other than that, it’s just basic elements - heading, text, and images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chadwick asks Ted asks a few questions about how the page layout changes at different breakpoints. Other than that, he thinks he’s got it.&lt;/p&gt;

&lt;h3 id=&quot;allies-review&quot;&gt;Allie’s review&lt;/h3&gt;

&lt;p&gt;Allie takes a content-first, accessibility-focused approach. For this design, she considers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Semantic HTML and reading order.&lt;/li&gt;
  &lt;li&gt;The way assistive technology will interact with the page.&lt;/li&gt;
  &lt;li&gt;Other ways that users might experience the page, or change the display.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This brings a few things to light:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;There is non-text content (images) that will need alternative text for those who can’t see the screen.&lt;/li&gt;
  &lt;li&gt;The banner under the “What about Wookiees” heading only exists on desktop.&lt;/li&gt;
  &lt;li&gt;The design dictates a change in reading order.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;She addresses these issues one at a time.&lt;/p&gt;

&lt;h4 id=&quot;1-missing-alternative-text&quot;&gt;(#1) Missing alternative text&lt;/h4&gt;

&lt;p&gt;She asks about #1 (alternative text for the images) first. Ted quickly scratches out some alternative text, and their project manager adds it to the ticket:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Image 1:&lt;/strong&gt; “An eight-year-old child with a bowl haircut, wearing pink corduroy pants and a baseball shirt with blue arms and E.T. on the front. She stands holding a cardboard cutout of an Ewok, in front of a Star Wars backdrop.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Image 2:&lt;/strong&gt; “A line sketch of a Wookiee, by an artist who is clearly quite skilled.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;2-content-exists-on-the-desktop-layout-that-is-missing-on-mobile&quot;&gt;(#2) Content exists on the desktop layout that is missing on mobile&lt;/h4&gt;

&lt;p&gt;Allie asks Ted about the “Did you know…” banner that is added on desktop. Ted decided to add this to give the page a bit more visual flair and help with the layout balance.&lt;/p&gt;

&lt;p&gt;Allie frowns. “So if someone uses a screen magnifier, they may trigger the mobile version of the page. That content will essentially never be available for that person. That’s also a violation of &lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/resize-text&quot;&gt;WCAG Success Criteria 1.4.4: Resize Text (Level AA)&lt;/a&gt;. Is that content important?”&lt;/p&gt;

&lt;h4 id=&quot;3-changes-in-reading-order&quot;&gt;(#3) Changes in reading order&lt;/h4&gt;

&lt;p&gt;She does a mental outline of the hierarchy and changes in the reading order.&lt;/p&gt;

&lt;p&gt;For now, she leaves out sectioning elements, like &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;article&amp;gt;&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;section&amp;gt;&lt;/code&gt;. However, she notes that they would be difficult to use properly on the desktop version.&lt;/p&gt;

&lt;div style=&quot;display: flex; flex-wrap: wrap; margin-bottom: 16px&quot;&gt;
  &lt;div style=&quot;margin: 4px;
              padding: 8px;
              border: 1px solid #928b80;
              border-radius: 4px;&quot;&gt;
    &lt;strong&gt;Mobile:&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;img:&lt;/strong&gt; Ewoks and et picture&lt;/li&gt;
      &lt;li&gt;
        &lt;strong&gt;h1:&lt;/strong&gt; Ewoks and E.T.: A Risky Mix
        &lt;ul&gt;
          &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; related content&lt;/li&gt;
          &lt;li&gt;
            &lt;strong&gt;h2:&lt;/strong&gt; Speeder bikes AND flying bicycles?
            &lt;ul&gt;
              &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; related content&lt;/li&gt;
              &lt;li&gt;
                &lt;strong&gt;h3:&lt;/strong&gt; Speeder bike technology
                &lt;ul&gt;
                  &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; related content&lt;/li&gt;
                &lt;/ul&gt;
              &lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;img:&lt;/strong&gt; Wookiee picture&lt;/li&gt;
          &lt;li&gt;
            &lt;strong&gt;h2:&lt;/strong&gt; But what about Wookiees
            &lt;ul&gt;
              &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; Wookiee-related content&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
  &lt;div style=&quot;margin: 4px; padding: 8px; border: 1px solid #928b80; border-radius: 4px;&quot;&gt;
    &lt;strong&gt;Desktop:&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;
        &lt;strong&gt;h1:&lt;/strong&gt; Ewoks and E.T.: A Risky Mix
        &lt;ul&gt;
          &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; related content&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;strong&gt;img:&lt;/strong&gt; Ewoks and E.T. picture
        &lt;ul&gt;
          &lt;li&gt;
            &lt;strong&gt;h2:&lt;/strong&gt; But what about Wookiees
            &lt;ul&gt;
              &lt;li&gt;&lt;strong&gt;p: Did you know? ...&lt;/strong&gt;&lt;/li&gt;
              &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; related content&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;strong&gt;h2:&lt;/strong&gt; Speeder bikes AND flying bicycles?
            &lt;ul&gt;
              &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; related content&lt;/li&gt;
              &lt;li&gt;
                &lt;strong&gt;h3:&lt;/strong&gt; Speeder bike technology
                &lt;ul&gt;
                  &lt;li&gt;&lt;strong&gt;p:&lt;/strong&gt; related content&lt;/li&gt;
                &lt;/ul&gt;
              &lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;img:&lt;/strong&gt; Wookiee picture&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Looking at the content this way, she notes that the order of the content changes significantly on desktop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One of these changes is fine.&lt;/strong&gt; The picture related to the “Ewoks and E.T.” content still stays tangential to the content. Whether before or after doesn’t affect meaning or understanding.&lt;/p&gt;

&lt;p&gt;However, the sections change reading order, and the Wookiee picture ends up no longer being in a section with its related text.&lt;/p&gt;

&lt;p&gt;Allie asks Ted about this. He explains that he wanted the Speeder bikes section to be above the fold. The column design was mostly used to help the image layout.&lt;/p&gt;

&lt;p&gt;“Unfortunately,” Allie says, “This will create a reading order that changes meaning on desktop. Not only for screen reader users—but also for desktop users that follow the vertical column flow to read. They will have a disjointed reading experience, where content is read out of order. This is a violation of &lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/meaningful-sequence&quot;&gt;WCAG Success Criteria 1.3.2: Meaningful Sequence (Level A)&lt;/a&gt;.”&lt;/p&gt;

&lt;h3 id=&quot;the-pushback&quot;&gt;The pushback&lt;/h3&gt;

&lt;p&gt;Chadwick is looking a little annoyed at this point. “Allie,” he says, “it’s not our job to question the design, we just implement it.”&lt;/p&gt;

&lt;p&gt;Allie shakes her head. “Sorry Chadwick, but it is our job to question a design if we can’t implement it in an accessible way. Not to mention, if the content retains the same order on desktop, there will be other benefits. Namely: performance, SEO, and code maintainability.”&lt;/p&gt;

&lt;p&gt;“For example, duplicating content means extra DOM nodes, which is bad for performance and code maintenance. When that content includes heading tags, it can also be bad for SEO.”&lt;/p&gt;

&lt;p&gt;Chadwick interjects: “Ok, fine, then we’ll use Javascript to move the content with a resize listener.”&lt;/p&gt;

&lt;p&gt;Allie replies, “That covers SEO, but is still affecting reading order, performance, and code maintenance.”&lt;/p&gt;

&lt;p&gt;“CSS grid? Rearranging the content in template areas?”&lt;/p&gt;

&lt;p&gt;Allie sighs. “Even if you could get that to work, you still would have an issue with the reading order. And any changes in the content would mean reworking the grid.”&lt;/p&gt;

&lt;p&gt;At this point, Ted jumps in. “No worries team. I had some of these thoughts myself when working on the design. I’ll take this one back to the drawing board for now, and we can look at it again in the next refinement session.”&lt;/p&gt;

&lt;h3 id=&quot;the-redesign&quot;&gt;The redesign&lt;/h3&gt;

&lt;p&gt;Two weeks pass, and Ted has come back with a new design. He’s thrilled to present it.&lt;/p&gt;

&lt;figure class=&quot;figcaption&quot;&gt;
  &lt;img class=&quot;figcaption__image&quot; style=&quot;border: none; border-radius: 0;&quot; src=&quot;/static/images/articles/accessibility-plus-content-design/designer-spec_desktop-fixed.png&quot; alt=&quot;screen capture of the fixed version of the deskop design, described in caption&quot; /&gt;
  &lt;figcaption class=&quot;figcaption__caption&quot;&gt;
    &lt;p&gt;&lt;strong&gt;Shown above:&lt;/strong&gt; The fixed desktop version of the layout.&lt;/p&gt;
    &lt;p&gt;In this version, the content is in the same general order as the mobile design. There are two rows this time. The first one has the image of the child on the left, and the paragraphs about &quot;Ewoks and ET&quot; and &quot;Speeder bikes&quot; on the right. The next row is centered with a border and contains the Wookiee-related text on the left, with the Wookiee drawing on the right.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;“In this new design, I can show the full picture of this fashionable child, including these astounding pink pants. This design should also work better at tablet size than the last one did.”&lt;/p&gt;

&lt;p&gt;Allie looks it over, doing a quick mental outline. Now all the content is in the same order/hierarchy, except for the Wookiee picture. But the picture is still tangential to the related content, so that’s fine. She gives a nod of approval. “It looks like you decided to get rid of that extra banner in the Wookiee section?”&lt;/p&gt;

&lt;p&gt;“Yeah,” Ted smiles sheepishly, “that never really fit with the purpose of the content. It was just filler - so I took it out.”&lt;/p&gt;

&lt;p&gt;Now both developers are happy with the design. Furthermore, Ted has learned to think in a more content-first manner, so his next design will take that into account.&lt;/p&gt;

&lt;p&gt;Was it a little awkward at first? Sure. But in the end, these tough conversations result in an improved experience for everyone.&lt;/p&gt;

&lt;p&gt;The new design has improved in many ways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It’s more performant and SEO-friendly.&lt;/li&gt;
  &lt;li&gt;The page has a more natural and readable flow for all users.&lt;/li&gt;
  &lt;li&gt;The code will be more maintainable, as it won’t need any hacks to work around an inaccessible design. &lt;/li&gt;
  &lt;li&gt;The developer won’t have to interrupt the designer after they’ve moved on to the next project (for example, to ask for alternative text).&lt;/li&gt;
  &lt;li&gt;And of course, the page will be accessible: delivering an equal experience for users with disabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottom line is, you can’t take any design and make it accessible. If the design itself is not accessible, it &lt;strong&gt;&lt;em&gt;is&lt;/em&gt;&lt;/strong&gt; your place to speak up as a developer.&lt;/p&gt;

&lt;p&gt;Both design and development are more likely to be accessible if they center around the content. If we can all align in that thinking, we can help each other improve.&lt;/p&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;h3 id=&quot;accessibility-basics&quot;&gt;Accessibility basics&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/WAI/&quot;&gt;Web Accessibility Initiative (WAI) | W3C&lt;/a&gt;&lt;/strong&gt;: A central hub for learning about accessibility fundamentals. Many of the resources provided below are within this space.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/WAI/fundamentals/accessibility-usability-inclusion/&quot;&gt;Accessibility, Usability, and Inclusion | WAI | W3C&lt;/a&gt;&lt;/strong&gt;: Understand how accessibility, usability, and inclusive design overlap, and their unique concerns.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/&quot;&gt;WCAG 2.1 Understanding Docs | WAI | W3C&lt;/a&gt;&lt;/strong&gt;: A deep dive into each of WCAG’s Success Criteria, explaining the intent of the rule, who it benefits, examples, and suggested techniques to meet that criterion.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/TR/WCAG21/&quot;&gt;WCAG (Web Content Accessibility Guidelines) | WAI | W3C&lt;/a&gt;&lt;/strong&gt;: The official specification, for when you’re ready for all the details.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;aria---accessible-rich-internet-applications&quot;&gt;ARIA - Accessible Rich Internet Applications&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/TR/using-aria/&quot;&gt;Using ARIA | WAI | W3C&lt;/a&gt;&lt;/strong&gt;: 5 really important rules to know before using ARIA, plus other tips and best practices.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/&quot;&gt;ARIA Authoring Practices Guide (APG) | WAI | W3C&lt;/a&gt;&lt;/strong&gt;: Component design patterns and examples, best practices, and much more.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.1/&quot;&gt;Accessible Rich Internet Applications (WAI-ARIA) 1.1 | WAI | W3C&lt;/a&gt;&lt;/strong&gt;: The official ARIA spec. This is a lot to dive into without some basic background first, I suggest starting with the links above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;semantic-html&quot;&gt;Semantic HTML&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML&quot;&gt;HTML: A good basis for accessibility | MDN web docs&lt;/a&gt;&lt;/strong&gt;: The basics of semantic HTML and the benefits it offers.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://24ways.org/2017/accessibility-through-semantic-html/&quot;&gt;Accessibility Through Semantic HTML | Laura Kalbag, 24 Ways&lt;/a&gt;&lt;/strong&gt;: A great summary of the benefits of semantic HTML, and how to test your work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;content-design&quot;&gt;Content Design&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://contentdesign.london/blog/what-is-content-design&quot;&gt;What is content design? | Content Design London&lt;/a&gt;&lt;/strong&gt;: A great intro for understanding content design and how it answers users’ needs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.writingisdesigning.com/&quot;&gt;Writing is Designing | Michael J. Metts and Andy Welfle (book)&lt;/a&gt;&lt;/strong&gt;: How to approach writing for the web as a form of design.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.prosekiln.com/blog/2020/11/17/the-massive-list-of-content-design-amp-ux-writing-resources&quot;&gt;The Massive List of Content Design &amp;amp; UX Writing Resources | Prose Kiln&lt;/a&gt;&lt;/strong&gt;: A list of books, articles, websites, courses, and much more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;tutorials-and-resources-for-beginners&quot;&gt;Tutorials and resources for beginners&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/WAI/courses/foundations-course/&quot;&gt;Digital Accessibility Foundations Free Online Course | W3C WAI&lt;/a&gt;&lt;/strong&gt;: An intro accessibility course for developers, designers, content authors, and anyone else who wants to learn the basics.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/learn/accessibility/&quot;&gt;Learn Accessibility | web.dev&lt;/a&gt;&lt;/strong&gt;: A great overview of the basics, and a good reference.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://testingaccessibility.com/buy&quot;&gt;Testing Accessibility Courses (paid course) | Marcy Sutton&lt;/a&gt;&lt;/strong&gt;:  A well-known accessibility advocate, Marcy offers three levels of courses with a hands-on, simplified approach to testing the accessibility of your web pages.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=pOUeNj7Pnc4&quot;&gt;Introduction to Screen Readers (video) | YouTube, Tim Harshbarger (A11yTalks - February 2021)&lt;/a&gt;&lt;/strong&gt;: Tim teaches about screen reader basics and how testing with screen readers is different from the way they’re actually used.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;tools-and-reference&quot;&gt;Tools and reference&lt;/h3&gt;

&lt;p&gt;There are far too many tools to list all the good ones. This list is slimmed down to some of those tools I use on a regular basis. Start with a few and see how they work for you.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/quickref/&quot;&gt;How to Meet WCAG (Quick Reference) | WAI | W3C&lt;/a&gt;&lt;/strong&gt;: A filterable quick reference list of all the WCAG criteria. A great way to find criteria by role, level, or subject (for example, “audio”).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://a11ysupport.io/&quot;&gt;Accessibility Support (allysupport.io)&lt;/a&gt;&lt;/strong&gt;: Check elements, attributes, and more to find details on feature support. While not always 100% up to date, it’s still one of the best tools for this task.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://accessibilityinsights.io/&quot;&gt;Accessibility Insights&lt;/a&gt;&lt;/strong&gt;: A testing tool created by Microsoft, available for Web (Chrome and Edge), Android, and Windows. It offers the ability to scan your page for issues using the axe-core library, as well as tools that support manual testing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://dequeuniversity.com/screenreaders/&quot;&gt;Screen Reader Keyboard Shortcuts and Gestures | Deque University&lt;/a&gt;&lt;/strong&gt;: Great shortcut guides for lots of different screen readers, in web or pdf versions.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.tpgi.com/color-contrast-checker/&quot;&gt;Color Contrast Checker | TPGi&lt;/a&gt;&lt;/strong&gt;: An installed application for Windows or macOS, it can test color contrast on live sites or design mock-ups with a simple eyedropper tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.mdpi.com/2227-9709/7/1/8&quot;&gt;Accessibility in Web Development Courses: A Case Study | MDPI&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Harmony Hames</name><email>hhames@rei.com</email></author><category term="frontend" /><category term="frontend" /><category term="accessibility" /><summary type="html">A silly story that helps illustrate the benefits of understanding both accessibility and content design.</summary></entry><entry><title type="html">The front-end build tool renaissance at REI</title><link href="https://engineering.rei.com/frontend/front-end-build-tool-renaissance.html" rel="alternate" type="text/html" title="The front-end build tool renaissance at REI" /><published>2023-02-02T00:00:00+00:00</published><updated>2023-02-02T00:00:00+00:00</updated><id>https://engineering.rei.com/frontend/front-end-build-tool-renaissance</id><content type="html" xml:base="https://engineering.rei.com/frontend/front-end-build-tool-renaissance.html">&lt;p&gt;The Front-end Build System (FEBS) is REI’s internally developed set of tools that helps engineers build the assets that are eventually downloaded by customers visiting REI’s online properties. FEBS has evolved over the years to support an ever-changing development landscape.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/images/articles/febs/rei-febs-logo.png&quot; alt=&quot;REI FEBS logo&quot; class=&quot;article-image-full-width&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The first generation of FEBS was built directly in our legacy monolithic code base, a series of scripts that leveraged some programmable APIs from libraries like &lt;code class=&quot;highlighter-rouge&quot;&gt;gulp&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;browserify&lt;/code&gt;. This code continues to serve any remaining applications living in that monolithic space.&lt;/p&gt;

&lt;p&gt;The Co-op moved toward a more decentralized approach by introducing microservice architecture with the development of the &lt;a href=&quot;https://engineering.rei.com/devops/how-we-built-a-microservices-platform.html&quot;&gt;Alpine platform&lt;/a&gt;. With this came a new generational iteration of &lt;a href=&quot;https://engineering.rei.com/frontend/the-rei-front-end-build-system.html&quot;&gt;FEBS&lt;/a&gt; that was decoupled from any individual implementation. FEBS 2 lives in REI’s internal NPM registry as &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; with several other supporting packages. It is declared as a dependency of an Alpine microsite.&lt;/p&gt;

&lt;p&gt;Under the hood, &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; is extending &lt;code class=&quot;highlighter-rouge&quot;&gt;webpack@4&lt;/code&gt; to bundle web application code and &lt;code class=&quot;highlighter-rouge&quot;&gt;rollup@2&lt;/code&gt; to bundle Vue 2 components and vanilla JavaScript libraries.&lt;/p&gt;

&lt;h2 id=&quot;unforeseen-consequences&quot;&gt;Unforeseen consequences&lt;/h2&gt;

&lt;p&gt;Our team maintains a microsite that contains supported patterns, technologies, and most importantly, an end-to-end functional example used as a reference by other product teams. We expected &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; to canonicalize the way teams built their front-end assets and thereby reduce any individual deviation from supported patterns, theoretically improving the interoperability ecosystem.&lt;/p&gt;

&lt;p&gt;For most cases, &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; did its job. However, as time went on, feature requests continued to pour in to accommodate product teams with unique business requirements. Additionally, the underlying dependencies supporting &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; continued to change, forcing us to manually reconcile them within our packages and within individual microsites.&lt;/p&gt;

&lt;p&gt;The maintenance of iterating on &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; and troubleshooting microsite build issues began to consume our daily lives. Worse was that &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; was becoming bloated with logic for many unforeseen use cases.&lt;/p&gt;

&lt;h2 id=&quot;inherit-apparent&quot;&gt;Inherit apparent&lt;/h2&gt;

&lt;p&gt;Perhaps you spotted the &lt;em&gt;inherent&lt;/em&gt; problem with &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt;? It’s an inheritance model. &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; extends several build tool technologies including &lt;code class=&quot;highlighter-rouge&quot;&gt;webpack&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;rollup&lt;/code&gt;. This means that &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;webpack&lt;/code&gt;. &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;rollup&lt;/code&gt;. So, what’s the issue? Inheritance models are brittle by nature (don’t @ me). Any change request can require a massive overhaul of the implementation. And the more you customize, the harder it becomes to maintain until it becomes an unrecognizable, enormous blob like in &lt;em&gt;Akira&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;this-is-the-way&quot;&gt;This is the way&lt;/h2&gt;

&lt;p&gt;Eventually, we hit a wall when Vue 3 arrived on the scene. We built a POC that brought Vue 3 support to &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt;. We discovered that the Rollup plugin used to compile Vue 3 SFCs for our public design system, &lt;a href=&quot;https://rei.github.io/rei-cedar-docs/&quot;&gt;Cedar&lt;/a&gt;, made tree-shaking incompatible with &lt;code class=&quot;highlighter-rouge&quot;&gt;webpack@4&lt;/code&gt;, resulting in bloated application bundles.&lt;/p&gt;

&lt;p&gt;Even though we had a functional POC of our reference microsite using Vue 3, we were unsatisfied with the low-level work we had to do to get there. We decided to experiment with a build toolchain overhaul that dispensed with our implementations in favor of using &lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt; to bundle our application. We quickly cobbled together a working solution. We liked the approach so much that we decided to move forward with it.&lt;/p&gt;

&lt;h2 id=&quot;enter-vite&quot;&gt;Enter Vite&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt; is a UI-framework agnostic build tool. It supports React, Svelte, Vue, and others. It’s a bundler … of bundlers. Ironically, it’s a wrapper that swaps bundlers in and out for different purposes. But the key point here is that it’s a wrapper maintained by an open-source community &lt;strong&gt;instead&lt;/strong&gt; of us. Under the hood, it uses &lt;code class=&quot;highlighter-rouge&quot;&gt;rollup&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;esbuild&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vite happens to have first-class support for Vue, which makes sense, as it was built by the creator of Vue. REI is a Vue shop. Our design system, &lt;a href=&quot;https://rei.github.io/rei-cedar-docs/&quot;&gt;Cedar&lt;/a&gt;, is a Vue component library. Cedar now uses Vite to bundle its code. We’ll use Vite to bundle our customer-facing Vue 3 applications. One tool to rule them all.&lt;/p&gt;

&lt;p&gt;Besides all that, Vite is pretty sweet. It has a “no-bundler” approach to running its development server. It doesn’t need to build your application before it’s served.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Vite serves source code over native ESM. This is essentially letting the browser take over part of the job of a bundler: Vite only needs to transform and serve source code on demand, as the browser requests it. Code behind conditional dynamic imports is only processed if actually used on the current screen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;so-what-is-febs-now&quot;&gt;So, what is FEBS now?&lt;/h2&gt;

&lt;p&gt;FEBS 3 is the “Front-end Build System”, but that system isn’t an explicit dependency.&lt;/p&gt;

&lt;p&gt;Rather, it’s a curated collection of open-source tools that are declared as dependencies of Alpine microsites and NPM packages used to bundle assets. Specifically Vite and Vitest. These tools are supplemented by REI-built configurations, plugins, and patterns to support REI-specific needs.&lt;/p&gt;

&lt;p&gt;This has alleviated us from low-level dependency management, makes future migrations and tool swaps easier, and allows engineers maximum flexibility with their application builds while always having a functional example to reference. Engineers can also lean directly on the open-source community for any issues they may have with a particular build tool.&lt;/p&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;/h2&gt;

&lt;p&gt;Reflecting on the evolution of the build toolchain at REI, the intentions were good. Create a tool that would normalize the way our teams built front-end assets and prevent deviation from supported patterns.&lt;/p&gt;

&lt;p&gt;What we learned was that we didn’t need to be restrictive in the form of an explicit tool like &lt;code class=&quot;highlighter-rouge&quot;&gt;@rei/febs&lt;/code&gt;. It’s not like people couldn’t get around it anyway. Instead, we simply provide sanctioned patterns and people use them. The benefits of using the raw build tool greatly outweigh a customization that extends the tool directly.&lt;/p&gt;</content><author><name>Kurt Medley</name><email>kmedley@rei.com</email></author><category term="frontend" /><category term="frontend" /><category term="tooling" /><summary type="html">The Front-end Build System (FEBS) is REI’s internally developed set of tools that helps engineers build the assets that are eventually downloaded by customers visiting REI’s online properties. FEBS has evolved over the years to support an ever-changing development landscape.</summary></entry></feed>