tag:blogger.com,1999:blog-4252363750415422552024-03-12T19:26:56.792-07:00Test Automation ArchitectureA history of my various learnings and mistakes on my path to automate all the things.Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.comBlogger23125tag:blogger.com,1999:blog-425236375041542255.post-76859206742437058582024-02-21T18:30:00.000-08:002024-02-22T10:11:31.371-08:00Uniform Output<p>In my current gig, we have more than one testing framework. For predominantly historical reasons, we have 3 different test automation frameworks at work. One in python, one in java, and one in javascript. </p><p>When I started here, I was given ownership of the team producing test automation in python. I set one of them up as the Framework Owner, responsible for all the framework changes, and supporting the rest of the team in the use of it. </p><p>And then my framework owner had an emergency and had to take a month's hiatus. I was left to fill his shoes. But I hadn't actually used the framework. I'd built some helpers, and built unit tests for them, but I hadn't even myself had to debug any of the cases. Much less rummage though the output to understand what was happening.</p><p>I wound up spending time to solve the short term problem. I added code to output the test status and debugging info I needed to be able to report results. When he got back, I removed that code. </p><p>And thought about that experience. And the fact that, where we've been, if we had a java opening, and a surplus python programmer, we'd probably fire the one and go looking for the other. Thing is, when that happens, we lose a lot of institutional knowledge. </p><p>And the language itself is the easy part. I learned python in a week by asking google how to do things I already knew how to do in other programming languages. I even won a "fastest code wins" competition with a developer at a company I was trying to get on with. In a week.</p><p>The harder part is finding the pieces in the code and the output. </p><p>As I ruminated on that, I was reminded that while I was at Home Depot, I was given the directive that every person with the company shall be able to read and understand the summary portions of the test result output. Which we did.</p><p>And I realized that if I had that same model, with all the pieces talked about below, not only would everybody be able to understand it, it would also speed up any debugging we needed to do. The reasons will become clear in the examples below.</p><p>And, if we implemented this same kind of output in all three of our frameworks, engineers who knew to look for these landmarks in the output of one would be able to switch to other projects, even ones where they had to learn a new programming language, and be able to add value within a week. So maybe we can keep folks we might otherwise have to lay off. </p><p>Work has given me leave to roll out Uniform Results. This doesn't replace any existing thing. In fact, we're also looking at moving all the frameworks to Allure for at least some of the reporting. But this will add to the console output, and may also eventually serve as a springboard for deploying a logging server (since now all the traffic will come though one set of pipes).</p><p>BANNERS</p><p>A banner is a chunk of output which looks like this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEihuaWgB6hS26nSyH3AiTYl7aRNgMuDFsp5G1MUkZeFJz2k2wIsK49FWTlWRuOk86NpeHpjD3rCDX_YCTVJzayT_St4dILYNJgg9cQxSr6EPYuKtX8JJLvadNwkG1v0I-t1_BXUiyMCGlU_fC68lgPOHuW8rI7h_6OrnvohLNg-7pEXsrjvi3KXWS40ZzI" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="371" data-original-width="1107" height="107" src="https://blogger.googleusercontent.com/img/a/AVvXsEihuaWgB6hS26nSyH3AiTYl7aRNgMuDFsp5G1MUkZeFJz2k2wIsK49FWTlWRuOk86NpeHpjD3rCDX_YCTVJzayT_St4dILYNJgg9cQxSr6EPYuKtX8JJLvadNwkG1v0I-t1_BXUiyMCGlU_fC68lgPOHuW8rI7h_6OrnvohLNg-7pEXsrjvi3KXWS40ZzI" width="320" /></a></div><div class="separator" style="clear: both; text-align: center;">(using images because Blogger didn't like the emoji)</div><br />This particular one is performing an assertion that a substring will be in a string. If instead it had been a failure from a selector not finding the element, then the fields would instead include things like what the selector was, how long the timeout was, and similar information. <p></p><p>My own experience has been that in the past I've often had to run a failed test again to get some missing piece of information. The point behind the banner is to never have to run the test again for more information. If you need to do that, you add that to the template for whatever kind of banner it is.</p><p>Banners get emitted for every assertion failure (including soft ones), or error.</p><p>TERMINAL REPORT</p><p>The Terminal Report comes at the end of the test run, and summarizes a lot of information. It comes in three parts:</p><p>TERMINAL REPORT: TEST ENVIRONMENT REPORT</p><p>This basically just lists anything about the test run which might be of value later in investigating any issues. This is a completely faked up example: </p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgVcOTRVmxyPtsJrgTrbF-OwdNwxbp7xx8AwZE0TyBCldUb22QlleKq0SHIjltpvi6dvwHwfaidV_VSX9FSQPn0hAicN9xhhlPYxYvHsO4ppfvYw1yS8xk1uTuboq8Ww9lhd1mv0_DHdin8vouF_K-8YZwT6zJSoa4iaulKTVs3n4a9o8CSkuE_EIzms9M" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="477" data-original-width="629" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEgVcOTRVmxyPtsJrgTrbF-OwdNwxbp7xx8AwZE0TyBCldUb22QlleKq0SHIjltpvi6dvwHwfaidV_VSX9FSQPn0hAicN9xhhlPYxYvHsO4ppfvYw1yS8xk1uTuboq8Ww9lhd1mv0_DHdin8vouF_K-8YZwT6zJSoa4iaulKTVs3n4a9o8CSkuE_EIzms9M" width="316" /></a></div><p><br /></p>TERMINAL REPORT: TEST CASE SUMMARY<p></p><p>For each test case, it could end with any of the following end states:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh8IONXhzpZv2E8UJq0W5gsm5sMlljhULWJ0dyZwtsnWFbGQxlvEl90k_lMH4T2N7-Psyp2u8JISPCRjr5vzGVmzeRdW8R7KwC3Rz8S_rtJ6TA_7wXyp01zptlfKXMi42kLFoo4awIAKYxPAgm2rP6KzY0OliDVDjsZfCZJ3USv4AJ3_y2WOlnTKeHVl2k" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="214" data-original-width="807" height="85" src="https://blogger.googleusercontent.com/img/a/AVvXsEh8IONXhzpZv2E8UJq0W5gsm5sMlljhULWJ0dyZwtsnWFbGQxlvEl90k_lMH4T2N7-Psyp2u8JISPCRjr5vzGVmzeRdW8R7KwC3Rz8S_rtJ6TA_7wXyp01zptlfKXMi42kLFoo4awIAKYxPAgm2rP6KzY0OliDVDjsZfCZJ3USv4AJ3_y2WOlnTKeHVl2k" width="320" /></a></div><p>We can attach marks to the test cases indicating if there's a bug against that case. This lets us report Unchanged and Fixed as a test status.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhd7TcGVS7Vui5nARKrO_3hM_phOb_FvTTaC4xblJz7qHYGyPBuq_QuLWUONX-v-FKLNF2SctXHimTMRSCsFZcr8ivF1FIJJ4xIE0Zr4gzjhwxApw12DNsrkkdBm2Z_13vAuY4w8IPVQ74ONnaNmYRFrnVxROdz1xXm0ULyu0wSjdTITj96hcUya__PV5k" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="701" data-original-width="807" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEhd7TcGVS7Vui5nARKrO_3hM_phOb_FvTTaC4xblJz7qHYGyPBuq_QuLWUONX-v-FKLNF2SctXHimTMRSCsFZcr8ivF1FIJJ4xIE0Zr4gzjhwxApw12DNsrkkdBm2Z_13vAuY4w8IPVQ74ONnaNmYRFrnVxROdz1xXm0ULyu0wSjdTITj96hcUya__PV5k" width="276" /></a></div><br />This shows status information for 3 cases, at least, most of 3. :) The middle one is a data set test, and row 6 suffered an assertion failure, while row 11 suffered an error.<p></p><p>TERMINAL REPORT: TEST RUN SUMMARY</p><p>This comes at the very end, and summarizes the whole run:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgVp254f-tHMOHOAL3KBnPlH3h5mvsnyO7OIfEfSCRNTZzXba-eM9lK37c4lSME9gEFHvsgMDMxLushyuATKJhdI-PyEr3hHOJpVTadKH8hfNCNKNk9TPahRix3bFG62t0qUGKFojmehweTrrM7nfxOL_jNH1_YPqyRgTjQcSOOMqwgjqpDoADKtEpd9OE" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="494" data-original-width="976" height="162" src="https://blogger.googleusercontent.com/img/a/AVvXsEgVp254f-tHMOHOAL3KBnPlH3h5mvsnyO7OIfEfSCRNTZzXba-eM9lK37c4lSME9gEFHvsgMDMxLushyuATKJhdI-PyEr3hHOJpVTadKH8hfNCNKNk9TPahRix3bFG62t0qUGKFojmehweTrrM7nfxOL_jNH1_YPqyRgTjQcSOOMqwgjqpDoADKtEpd9OE" width="320" /></a></div><br /><br /><p></p><br /><br /><p></p><p><br /></p>Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-49892561732107809522023-11-08T11:29:00.005-08:002023-11-08T11:31:08.467-08:00On the history of test automation...<p>I was watching a fabulous video on test automation and "flows" here: https://www.youtube.com/watch?v=706G6v2kMzQ</p><p>But his notion of history is a bit out of sync. This is my reply:</p><p>An interesting point, you say the page objects, UI objects and intermediate layers were build from early 2000s to 2015... </p><p>SilkTest is a Windows automated testing product. It was shipped in beta form in 1991 by Segue software (Marsten Parker and Dave Laroche). It had page objects, page object methods, and control objects. These features, to one extent or another, were common in commercial tools for the Windows platform by 2000. </p><p>I myself was adding an abstraction layer between pages and tests to isolate the tests from the UI in 1991. At that time I was abstracting features, but in 2015, I switched to user role centric flows. Much easier to teach my team to think in terms of designing tests for flows rather than features. :)</p><p>I had a conversation with whoever was leading Mozilla's efforts in 2010 about bringing all this to Selenium back then. I build it in 2015, and there's a newer version here: https://github.com/akienm/swadl which also supports engine driven testing, where the page objects describe what needs to be tested about each control, and a single call does it all. https://testautomationarchitecture.blogspot.com/2021/10/engine-driven-gui-testing-design-pattern.html</p><p><br /></p>Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-72997174496717856432021-10-06T17:27:00.015-07:002021-10-18T16:57:57.305-07:00Engine Driven GUI Testing Design Pattern<h2 style="text-align: left;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: x-large; font-weight: normal;"><span data-renderer-mark="true" face="-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif" style="letter-spacing: -0.008em; white-space: pre-wrap;">Problem Statement:</span></span></h2><h2 data-renderer-start-pos="1" id="Problem-Statement:" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 1.43em; font-weight: normal; letter-spacing: -0.008em; line-height: 1.2; margin: 0px; padding: 0px; white-space: pre-wrap;"><span class="heading-anchor-wrapper" role="presentation" style="height: 1.2em; margin-left: 6px; position: absolute;"><button class="sc-gZMcBi kXdsSQ" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-outline: initial; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1ncnk3i" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; --icon-primary-color: #6B778C; --icon-secondary-color: #FFFFFF; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-bgcolor="" fill-rule="evenodd" fill="currentColor" style="--darkreader-inline-bgcolor: #0d0d0d; background-color: black;"><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></g></svg></span></button></span></h2><p data-renderer-start-pos="21" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Most GUI automation tests redo the same pattern repeatedly:</span></p><ul class="ak-ul" data-indent-level="1" style="box-sizing: border-box; display: flow-root; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; margin: 10px 0px 0px; padding: 0px 0px 0px 24px; white-space: pre-wrap;"><li><p data-renderer-start-pos="84" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Controls are represented by a selector</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="126" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Page objects encapsulate selectors and often methods which make use of the controls</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="213" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Either tests or page methods manipulate these controls</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="271" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Either tests or page methods validate the result</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="323" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">And we write these little pieces of code over and over for each action or validation we want to do</span></p></li></ul><p data-renderer-start-pos="425" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">There is a lot of duplication in this model, even when it's done really well. </span></p><h3 data-renderer-start-pos="505" id="Justification-Intent:" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; letter-spacing: -0.006em; line-height: 1.5; margin: 2em 0px 0px; padding: 0px; white-space: pre-wrap;"></h3><h2 style="text-align: left;"><span data-renderer-mark="true" style="font-weight: normal;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: x-large;">Justification Intent:</span></span></h2><span class="heading-anchor-wrapper" role="presentation" style="font-size: 1.142em; height: 1.25em; margin-left: 6px; position: absolute;"><button class="sc-gZMcBi kXdsSQ" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-outline: initial; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1ncnk3i" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; --icon-primary-color: #6B778C; --icon-secondary-color: #FFFFFF; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-bgcolor="" fill-rule="evenodd" fill="currentColor" style="--darkreader-inline-bgcolor: #0d0d0d; background-color: black;"><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></g></svg></span></button></span><p data-renderer-start-pos="528" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">By building an engine to validate all the controls on a page, and validating them whenever the page is loaded or changed, automation can catch things otherwise missed.</span></p><h2 style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; text-align: left; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: x-large; font-weight: normal;">Description:</span></h2><p data-renderer-start-pos="708" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Reduce automated testing to writing descriptions of pages and data, which are then processed by an engine. No additional coding is required for most tests.</span></p><p data-renderer-start-pos="865" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">A broadly applicable special class of data driven test, where the data provided describes not only the test conditions, but also how the page is manipulated given said data.</span></p><h2 data-renderer-start-pos="1040" id="Detail:" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; letter-spacing: -0.008em; line-height: 1.2; margin: 1.8em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-renderer-mark="true" style="font-weight: normal;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: x-large;">Detail:</span></span><span class="heading-anchor-wrapper" role="presentation" style="font-size: 1.43em; font-weight: normal; height: 1.2em; margin-left: 6px; position: absolute;"><button class="sc-gZMcBi kXdsSQ" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-outline: initial; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1ncnk3i" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; --icon-primary-color: #6B778C; --icon-secondary-color: #FFFFFF; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-bgcolor="" fill-rule="evenodd" fill="currentColor" style="--darkreader-inline-bgcolor: #0d0d0d; background-color: black;"><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></g></svg></span></button></span></h2><p data-renderer-start-pos="1049" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">In engine driven testing, we provide lists of controls to check, with not only the attributes for the control, but also attributes that described how the control was to be tested. The engine would then process the control lists.</span></p><h3 data-renderer-start-pos="1279" id="Example" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; letter-spacing: -0.006em; line-height: 1.5; margin: 2em 0px 0px; padding: 0px; white-space: pre-wrap;"></h3><h3 style="text-align: left;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: large; font-weight: normal;">Example:</span></h3><span class="heading-anchor-wrapper" role="presentation" style="font-size: 1.142em; height: 1.25em; margin-left: 6px; position: absolute;"><button class="sc-gZMcBi kXdsSQ" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-outline: initial; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1ncnk3i" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; --icon-primary-color: #6B778C; --icon-secondary-color: #FFFFFF; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-bgcolor="" fill-rule="evenodd" fill="currentColor" style="--darkreader-inline-bgcolor: #0d0d0d; background-color: black;"><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></g></svg></span></button></span><p data-renderer-start-pos="1288" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Controls become collections of data that include not only identification data (eg a selector string) but also includes the information required in order to determine how to test the control. Tests provide test data (eg username, password, first name, and so on). An engine steps through the set of controls, applying or capturing data as directed.</span></p><p data-renderer-start-pos="1637" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Test data includes information like:</span></p><p data-renderer-start-pos="1637" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;"><span style="font-family: courier; font-size: 14px; letter-spacing: normal; white-space: pre;">{'first_name': 'FQAUser', 'last_name': 'LQAUser'}</span></span></p><p data-renderer-start-pos="1637" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;"><span style="letter-spacing: -0.08px;">Control data, on the other hand, includes data similar to that shown in the example below:</span></span></p><div class="code-block sc-iqzUVk guyeDo" style="border-radius: 3px; clear: both; display: grid; font-size: 16px; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-qrIAp fbKXfG" face="-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif" style="-webkit-box-pack: end; display: flex; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-jiizgi" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background-attachment: initial; background-clip: initial; background-image: none; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; font-style: normal; font-weight: 500; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><br /><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-pxzk9z" role="img" style="--icon-primary-color: currentColor; --icon-secondary-color: #FFFFFF; display: inline-block; flex-shrink: 0; line-height: 1;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-bgcolor="" fill="currentColor" style="--darkreader-inline-bgcolor: #0d0d0d; background-color: black;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;"><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></g></svg></span></span></button></div></div></span></div><div class="code-block sc-iqzUVk guyeDo" style="border-radius: 3px; clear: both; display: grid; font-size: 16px; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-qrIAp fbKXfG" face="-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif" style="-webkit-box-pack: end; display: flex; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-jiizgi" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background-attachment: initial; background-clip: initial; background-image: none; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; font-style: normal; font-weight: 500; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><br /></button><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-jiizgi" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background-attachment: initial; background-clip: initial; background-image: none; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; font-style: normal; font-weight: 500; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><br /></button><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-jiizgi" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background-attachment: initial; background-clip: initial; background-image: none; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; font-style: normal; font-weight: 500; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><br /></button></div></div></span></div><p data-renderer-start-pos="2257" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;"><span style="font-family: courier; font-size: 14px; letter-spacing: normal; white-space: pre;">my_control = SWADLControl(
</span><span style="font-family: courier; font-size: 14px; letter-spacing: normal; white-space: pre;"> selector="blahblah",
is_text="Please enter your first name",
test_data_key="first_name",
actual_value_key="actual_first_name",
validations={
VERIFY_EXIST: True,
VERIFY_VISIBLE: True,
VERIFY_ENABLED: True,
VERIFY_UNIQUE: True,
VERIFY_VALUE: "Please enter your first name",
VERIFY_PROPERTY: ("selenium", "first_name"),
VERIFY_INPUT: True,
},
)</span></span></p><p data-renderer-start-pos="2257" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Using the <span style="font-family: courier; font-size: 14px; letter-spacing: normal; white-space: pre;">test_data_key</span> as the pointer into the test data, this then is enough to scan the input control and take a value from the test data to apply to the control.</span></p><p data-renderer-start-pos="2419" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Based on the control type, additional information is included in the control such as the expected value for labels, link targets for links and so on... And this then tells the engine how to manipulate and validate the control and it's contents. </span></p><p data-renderer-start-pos="2666" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Values to capture for later evaluation are captured if a <span style="font-family: courier; font-size: 14px; letter-spacing: normal; white-space: pre;">actual_value_key </span>is specified in the control's data. </span></p><p data-renderer-start-pos="2772" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">The engine then uses data control's data to determine which tests to perform:</span></p><ul class="ak-ul" data-indent-level="1" style="box-sizing: border-box; display: flow-root; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; margin: 10px 0px 0px; padding: 0px 0px 0px 24px; white-space: pre-wrap;"><li><p data-renderer-start-pos="2853" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify exist</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="2869" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify visible</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="2887" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify enabled</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="2905" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify unique</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="2922" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify text value</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="2943" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify attribute value</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="2969" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify clickable = verify that it exists, is visible, is enabled, and is unique.</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="3053" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify Input = Set a value (from the <span style="font-family: courier; font-size: 14px; letter-spacing: normal; white-space: pre;">test_data_key</span>, for relevant types)</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="3125" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Verify Click = Click on the thing</span></p></li></ul><p data-renderer-start-pos="3162" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Or other tests as needed. Some instructions are implicit... Eg you don't need to test the default text on a label if it's expected value isn't in the verify data.</span></p><p data-renderer-start-pos="3334" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Control information can also contain flags such as "any error with this control is fatal", or "this control is optional/required, and so on.</span></p><p data-renderer-start-pos="3476" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">By adding another test to the engine, all the existing controls that are eligible for the new test also get tested, provided they have all the data needed. </span></p><p data-renderer-start-pos="3634" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Of course not all tests will fit into this pattern, but in our experience, most will.</span></p><p data-renderer-start-pos="3721" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Collections of controls then can simply iterate through all the controls, calling the engine for each one.</span></p><h3 style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; text-align: left; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: large; font-weight: normal;">Example:</span></h3><div style="font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; text-align: left; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;">my_control = SWADLControl( <br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> selector="blahblah",<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> is_text="Please enter your first name", <br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> test_data_key="first_name", <br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> actual_value_key="actual_first_name",<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> validations={<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> VERIFY_EXIST: True,<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> VERIFY_VISIBLE: True,<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> VERIFY_ENABLED: True,<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> VERIFY_UNIQUE: True,<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> VERIFY_VALUE: "Please enter your first name",<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> VERIFY_PROPERTY: ("selenium", "first_name"),<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> VERIFY_INPUT: True,<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;"> },<br /></span></span><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-family: courier;"><span style="font-size: 14px; letter-spacing: normal; white-space: pre;">)</span></span></div><div class="code-block sc-iqzUVk guyeDo" style="border-radius: 3px; clear: both; display: grid; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-qrIAp fbKXfG" style="-webkit-box-pack: end; display: flex; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-jiizgi" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background-attachment: initial; background-clip: initial; background-image: none; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; font-style: normal; font-weight: 500; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><br /></button><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-jiizgi" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background-attachment: initial; background-clip: initial; background-image: none; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; font-style: normal; font-weight: 500; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><br /></button></div></div></span></div><p data-renderer-start-pos="4278" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">This defines a control (in python). No flows or page methods will typically access this control other than via an inherited <code class="code css-9z42f9" data-renderer-mark="true" style="--ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">process_control(passed_dict)</code> method.</span></p><p data-renderer-start-pos="4440" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">In the example above, self refers to the page. </span></p><p data-renderer-start-pos="4489" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">While this example is in python, it would even be possible to implement these descriptions as xml instead of code.</span></p><h3 data-renderer-start-pos="4605" id="Engine" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; letter-spacing: -0.006em; line-height: 1.5; margin: 2em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: large; font-weight: normal;">Engine:</span><span class="heading-anchor-wrapper" role="presentation" style="font-size: 1.142em; height: 1.25em; margin-left: 6px; position: absolute;"><button class="sc-gZMcBi kXdsSQ" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-outline: initial; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1ncnk3i" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; --icon-primary-color: #6B778C; --icon-secondary-color: #FFFFFF; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-bgcolor="" fill-rule="evenodd" fill="currentColor" style="--darkreader-inline-bgcolor: #0d0d0d; background-color: black;"><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></g></svg></span></button></span></h3><p data-renderer-start-pos="4613" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">The engine is simply a set of conditionals, like if <code class="code css-9z42f9" data-renderer-mark="true" style="--ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">control.validations["verify_visible"]==True</code> then <code class="code css-9z42f9" data-renderer-mark="true" style="--ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">assert control.get_visible()</code>.</span></p><p data-renderer-start-pos="4745" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Some control classes will take data and some won't. Those if statements will prevent the wrong thing from happening with the wrong control class. For example, if it's a label type control, we won't go looking to see if there's a value to put in the control. </span></p><p data-renderer-start-pos="5005" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">For any special case control, we just replace the <code class="code css-9z42f9" data-renderer-mark="true" style="--ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">process_control()</code> method with a custom one. </span></p><h3 data-renderer-start-pos="5101" id="Personal-Experience:" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; letter-spacing: -0.006em; line-height: 1.5; margin: 2em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-renderer-mark="true" style="font-weight: normal;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; font-size: large;">Personal Experience:</span></span><span class="heading-anchor-wrapper" role="presentation" style="font-size: 1.142em; height: 1.25em; margin-left: 6px; position: absolute;"><button class="sc-gZMcBi kXdsSQ" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-outline="" style="--darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-outline: initial; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1ncnk3i" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; --icon-primary-color: #6B778C; --icon-secondary-color: #FFFFFF; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-bgcolor="" fill-rule="evenodd" fill="currentColor" style="--darkreader-inline-bgcolor: #0d0d0d; background-color: black;"><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></g></svg></span></button></span></h3><p data-renderer-start-pos="5123" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">At Home Depot, we implemented this using wrapper objects for the controls, where the wrapper carried the selector, but also flags to indicate what was to be tested and what the expected result was. Then we just ran a loop to validate all the controls, calling the engine. The engine then read the flags so it knew what to test.</span></p><p data-renderer-start-pos="5453" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">When confronted with significant changes to the AUT, we nonetheless were able to turn around our fixes in a matter of moments.</span></p>Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-57281743446111557572021-07-12T14:59:00.016-07:002022-09-02T12:04:45.155-07:00Layers 101: Test Automation and the The Flow Design Pattern <h2 data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-color="" data-renderer-start-pos="2" id="Introduction" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-border-bottom: #484b4b; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-bottom-color: rgb(204, 204, 204); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 1.43em; font-weight: normal; letter-spacing: -0.008em; line-height: 1.2; margin: 1.8em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Introduction</span><span class="heading-anchor-wrapper" role="presentation" style="height: 1.2em; margin-left: 6px; position: absolute;"><button class="sc-hzDkRC dsymMa" data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: transparent; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; background-color: transparent; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1i2mldy" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g fill-rule="evenodd" fill="currentColor"></g></svg><span><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></span></span></button></span></h2><h4><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="16" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Of all the design patterns that contribute to a good test automation environment, the <a class="sc-gzOgki dwCPUL" data-darkreader-inline-color="" data-renderer-mark="true" href="https://en.wikipedia.org/wiki/Layer_(object-oriented_design)" style="--darkreader-inline-color: #63a9e8; text-decoration-line: none;" title="https://en.wikipedia.org/wiki/Layer_(object-oriented_design)"><strong data-renderer-mark="true">layers design pattern</strong></a> is the single most important. </span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="156" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">The layered architecture pattern divides the components into a number of horizontal layers. These layers each know how to make requests of the lower layers, but not how the lower layer does it’s work (<a class="sc-gzOgki dwCPUL" data-darkreader-inline-color="" data-renderer-mark="true" href="https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)" style="--darkreader-inline-color: #63a9e8; text-decoration-line: none;" title="https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)"><strong data-renderer-mark="true">encapsulation</strong></a>). This is the traditional method for designing most software. </span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="435" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">The central idea is to use structured divisions to break the code into pieces who each know how to ask other pieces to do things, but have no idea how it’s done. The internet is built this way. Your home router doesn’t know how the web servers do what they do, it just hands you the packets addressed to you. Layers above that in your computer don’t know how the packets get here, but does know how to reassemble them. This isolation of functionality is why you don’t have to replace your home router when a new kind of service, such as email or chat, is added.</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="998" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">What follows is a discussion with many examples of pseudocode. <strong data-renderer-mark="true">The same layers discussed below are applicable whether you’re using a code free or low code tool as they are for any of the coded tools. </strong></span></p></h4><h2 data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-color="" data-renderer-start-pos="1200" id="Layers-Tutorial-and-Example-Use-Case" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-border-bottom: #484b4b; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-bottom-color: rgb(204, 204, 204); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 1.43em; font-weight: normal; letter-spacing: -0.008em; line-height: 1.2; margin: 1.8em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Layers Tutorial and Example Use Case</span><span class="heading-anchor-wrapper" role="presentation" style="height: 1.2em; margin-left: 6px; position: absolute;"><button class="sc-hzDkRC dsymMa" data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: transparent; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; background-color: transparent; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1i2mldy" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g fill-rule="evenodd" fill="currentColor"></g></svg><span><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></span></span></button></span></h2><h4><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="1238" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Imagine we had thousands of tests. And that they look sorta like this:</span></p><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e72c2a; color: red;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-10fl9lg" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(2ch + 16px),transparent calc(2ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666; font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>function Test1()
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> EmailAddr = "BobSmith@foo.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> NewEmailAddr = "BobSmith@bar.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> PassWord = "secret"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span> Driver.OpenPage(globals.env_url)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span> Driver.WaitForControlVisible("user-name-selector", 20)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> Driver.SendKeys("user-name-selector", EmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">8</span> Driver.SendKeys("password-selector", PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">9</span> Driver.Click("login-button-selector")
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">10</span> assert Driver.GetVisible("hero-image-selector")
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">11</span> Driver.Click("my-account-selector")
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">12</span> assert Driver.GetVisible("my-account-change-email-button-selector")
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">13</span> Driver.Click("my-account-change-email-button-selector")
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">14</span> Driver.TypeKeys("my-account-email-box-selector", NewEmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">15</span> Driver.Click("my-account-save-button-selector")</span><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e72c2a; color: red; font-family: unset; font-size: unset;">
</span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="2010" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">We have a test which logs in, and changes the user’s email. We eventually hope to have thousands of tests like this.</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="2128" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Imagine if every test case has to log in, so they all start with that part of this code. Now imagine that the <strong data-renderer-mark="true">selector</strong> (strings which define how to find that control on the page) <code class="code css-9z42f9" data-darkreader-inline-bgcolor="" data-renderer-mark="true" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">user-name-selector</code> changes. That means we could have thousands of places to change it.</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="2395" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">To solve this, we have to move the selectors out of the tests. To do this, we use the layers pattern to create a new place to put the selectors.</span></p><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e72c2a; color: red;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-10fl9lg" data-code-lang="text" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code class="language-text" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(2ch + 16px),transparent calc(2ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666; font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>function Test1()
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> EmailAddr = "BobSmith@foo.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> NewEmailAddr = "BobSmith@bar.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> PassWord = "secret"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span> Driver.OpenPage(globals.env_url)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span> Driver.WaitForControlVisible(LoginPage.EmailAddrSelector, 20)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> Driver.SendKeys(LoginPage.EmailAddrSelector, EmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">8</span> Driver.SendKeys(LoginPage.PassWordSelector, PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">9</span> Driver.Click(LoginPage.LoginButton.Selector)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">10</span> assert Driver.GetVisible(LoggedInPage.HeroImage.Selector)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">11</span> Driver.Click(LoggedInPage.MyAccountSelector)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">12</span> assert Driver.GetVisible(MyAccountPage.ChangeEmailButtonSelector)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">13</span> Driver.Click(MyAccountPage.ChangeEmailButtonSelector)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">14</span> Driver.TypeKeys(MyAccountPage.EmailBoxSelector, NewEmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">15</span> Driver.Click(MyAccountPage.SaveButtonSelector")</span><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e72c2a; color: red; font-family: unset;">
</span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="3283" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">In this example, there are two layers: the <strong data-renderer-mark="true">test</strong> and the <strong data-renderer-mark="true">page</strong>. The Page has the selectors for the controls we’re going to manipulate. The test sets up the data, loads the page, uses the selectors to populate the fields, and click Login. Having a separate page object is called <strong data-renderer-mark="true">The </strong><a class="sc-gzOgki dwCPUL" data-darkreader-inline-color="" data-renderer-mark="true" href="https://www.selenium.dev/documentation/en/guidelines_and_recommendations/page_object_models/" style="--darkreader-inline-color: #63a9e8; text-decoration-line: none;" title="https://www.selenium.dev/documentation/en/guidelines_and_recommendations/page_object_models/"><strong data-renderer-mark="true">Page Object Pattern</strong></a>.</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="3585" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">In this case, if the selector changes, when we next run the tests, they will fail. But we only have to update the page, and all the tests will work once again.</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="3746" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">In this example, the two layers are <strong data-renderer-mark="true">tightly coupled</strong>. So the test knows how the page is set up, and manipulates it to make things happen. </span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="3885" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;"><strong data-renderer-mark="true">In general, having the tests tightly coupled means that any time I make a change to the page that’s greater than just changing a selector, I have to also change all the tests that do the same thing. We want </strong><a class="sc-gzOgki dwCPUL" data-darkreader-inline-color="" data-renderer-mark="true" href="https://en.wikipedia.org/wiki/Loose_coupling" style="--darkreader-inline-color: #63a9e8; text-decoration-line: none;" title="https://en.wikipedia.org/wiki/Loose_coupling"><strong data-renderer-mark="true">Loosely Coupled</strong></a><strong data-renderer-mark="true"> components to make the code more maintainable.</strong></span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="4156" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">This is however a login, and many tests will have to do that. So we turn it into functions, and now we can do this:</span></p><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e72c2a; color: red;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-10fl9lg" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(2ch + 16px),transparent calc(2ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>function Test1()
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> EmailAddr = "BobSmith@foo.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> NewEmailAddr = "BobSmith@bar.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> PassWord = "secret"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span> LoginPage.OpenLoginPage()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span> LoginPage.PopulateUser(EmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> LoginPage.PopulatePassword(PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">8</span> LoginPage.Submit()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">9</span> LoggedInPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">10</span> LoggedInPage.OpenMyAccount()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">11</span> MyAccountPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">12</span> MyAccountPage.EnterEmail(NewEmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">13</span> MyAccountPage.Save()</span><span style="font-family: unset;">
</span></span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="4686" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">No more pesky selectors in the test! We have created multiple <strong data-renderer-mark="true">methods</strong> (functions belonging to an object class) on the login page, which do part of the work for us. This is even less tightly coupled. </span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="4887" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">We can simplify this further, by moving the individual actions into a <strong data-renderer-mark="true">wrapper function</strong> which does all the login for us. </span></p><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-1xfvm4v" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(1ch + 16px),transparent calc(1ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>function Test1()
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> EmailAddr = "BobSmith@foo.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> NewEmailAddr = "BobSmith@bar.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> PassWord = "secret"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span> LoginPage.Login(EmailAddr, PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span> LoggedInPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> LoggedInPage.OpenMyAccount()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">8</span> MyAccountPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">9</span> MyAccountPage.EditEmail(NewEmailAddr)</span><span style="font-family: unset;">
</span></span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="5305" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;"><code class="code css-9z42f9" data-darkreader-inline-bgcolor="" data-renderer-mark="true" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">EditEmail</code> now also performs the Save operation. <a class="sc-gzOgki dwCPUL" data-darkreader-inline-color="" data-renderer-mark="true" href="https://simpsons.fandom.com/wiki/Woo-hoo!" style="--darkreader-inline-color: #63a9e8; text-decoration-line: none;" title="https://simpsons.fandom.com/wiki/Woo-hoo!">Woo hoo</a>! Much simpler! And if the login function changes radically, but is still on one page, we just update the one page, and all the tests now work again.</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="5511" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">But life is seldom so simple, so now let’s make our login more complicated. Let’s say it has a second page, with a <a class="sc-gzOgki dwCPUL" data-darkreader-inline-color="" data-renderer-mark="true" href="https://en.wikipedia.org/wiki/Multi-factor_authentication" style="--darkreader-inline-color: #63a9e8; text-decoration-line: none;" title="https://en.wikipedia.org/wiki/Multi-factor_authentication">Two Factor Authentication</a> (2FA).</span></p><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-10fl9lg" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(2ch + 16px),transparent calc(2ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>function Test1()
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> EmailAddr = "BobSmith@foo.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> NewEmailAddr = "BobSmith@bar.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> PassWord = "secret"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span> TwoFactorAuth = "somethingelse"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span> LoginPage.Login(EmailAddr, PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> TwoFAPage.Enter2FA(TwoFactorAuth)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">8</span> LoggedInPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">9</span> LoggedInPage.OpenMyAccount()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">10</span> MyAccountPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">11</span> MyAccountPage.EditEmail(NewEmailAddr)</span><span style="font-family: unset;">
</span></span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="6030" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Now every time we log in, and do 2FA, we have to call those in every test. And if that changes, we’re still back to updating all the tests.</span></p></h4><h2 data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-color="" data-renderer-start-pos="6171" id="The-Flows-Design-Pattern" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-border-bottom: #484b4b; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-bottom-color: rgb(204, 204, 204); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 1.43em; font-weight: normal; letter-spacing: -0.008em; line-height: 1.2; margin: 1.8em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">The Flows Design Pattern</span><span class="heading-anchor-wrapper" role="presentation" style="height: 1.2em; margin-left: 6px; position: absolute;"><button class="sc-hzDkRC dsymMa" data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: transparent; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; background-color: transparent; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1i2mldy" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g fill-rule="evenodd" fill="currentColor"></g></svg><span><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></span></span></button></span></h2><h4><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="6197" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">So to really make this robust, we need a way to get that UI specific functionality out of the test. So we make a new layer. This one will be called <strong data-renderer-mark="true">flows</strong>. (Some tools and practitioners use the term <strong data-renderer-mark="true">storyboarding</strong>.)</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="6412" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">A flow is any action a user might do. So logging in is a <em data-renderer-mark="true">user action</em>, rather than an action specific to individual pages. </span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="6536" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Let’s look at that:</span></p><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-1xfvm4v" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(1ch + 16px),transparent calc(1ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>function Test1()
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> EmailAddr = "BobSmith@foo.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> NewEmailAddr = "BobSmith@bar.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> PassWord = "secret"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span> TwoFactorAuth = "somethingelse"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span> LoginFlow.Login(EmailAddr, PassWord, TwoFactorAuth)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> MyAccountFlow.UpdateEmail(NewEmailAddr)</span><span style="font-family: unset;">
</span></span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="6808" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">This example is about as close as we can get to the test thinking about what’s to be done the way a human would. This example is clearly incomplete, so let’s show a more complete version of this example.</span></p></h4><h2 data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-color="" data-renderer-start-pos="7013" id="Development-Example" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-border-bottom: #484b4b; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-bottom-color: rgb(204, 204, 204); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 1.43em; font-weight: normal; letter-spacing: -0.008em; line-height: 1.2; margin: 1.8em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Development Example</span><span class="heading-anchor-wrapper" role="presentation" style="height: 1.2em; margin-left: 6px; position: absolute;"><button class="sc-hzDkRC dsymMa" data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: transparent; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; background-color: transparent; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1i2mldy" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g fill-rule="evenodd" fill="currentColor"></g></svg><span><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></span></span></button></span></h2><h4><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="7034" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">We’ll tackle this in the order I usually do when developing a test. Your mileage may vary…</span></p><ul class="ak-ul" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-indent-level="1" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; box-sizing: border-box; display: flow-root; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 0px; padding: 0px 0px 0px 24px; white-space: pre-wrap;"><li><p data-renderer-start-pos="7128" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">We know we want to change the email address of somebody, so we make sure we have a somebody in the system.</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="7238" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">We also need an address to change to</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="7278" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">And the login password and 2FA</span></p></li></ul><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-1xfvm4v" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(1ch + 16px),transparent calc(1ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>function Test1()
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> EmailAddr = "BobSmith@foo.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> NewEmailAddr = "BobSmith@bar.com"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> PassWord = "secret"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span> TwoFactorAuth = "somethingelse"</span><span style="font-family: unset;">
</span></span></code></span></div><ul class="ak-ul" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-indent-level="1" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; box-sizing: border-box; display: flow-root; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 0px; padding: 0px 0px 0px 24px; white-space: pre-wrap;"><li><p data-renderer-start-pos="7465" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">And we’re going to need two flows, one which can log in, and one which can change the email.</span></p></li></ul><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-1xfvm4v" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(1ch + 16px),transparent calc(1ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span> LoginFlow.Login(EmailAddr, PassWord, TwoFactorAuth)
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> MyAccountFlow.UpdateEmail(NewEmailAddr)</span><span style="font-family: unset;">
</span></span></code></span></div><ul class="ak-ul" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-indent-level="1" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; box-sizing: border-box; display: flow-root; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 0px; padding: 0px 0px 0px 24px; white-space: pre-wrap;"><li><p data-renderer-start-pos="7664" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Yes, this does look just like the example above, but this is the order I do it in. The important point here is that right now, at this point in the process, I have a test. But it won’t run. <code class="code css-9z42f9" data-darkreader-inline-bgcolor="" data-renderer-mark="true" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">LoginFlow</code> and <code class="code css-9z42f9" data-darkreader-inline-bgcolor="" data-renderer-mark="true" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">MyAccountFlow</code> don’t yet exist. So that’s what’s next. I know I’m gonna need a login page object, a logged in page object, and a my account page... <em data-renderer-mark="true">But I can specify them without them existing yet. (Some tools, such as Worksoft Certify, may require stubbing such routines until the new ones are ready.)</em></span></p></li></ul><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-10fl9lg" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(2ch + 16px),transparent calc(2ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>Class LoginFlow
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> Function Login(EmailAddr, PassWord, TwoFactorAuth)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> LoginPage.Login(EmailAddr, PassWord, TwoFactorAuth)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> LoggedInPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span>
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span>Class MyAccountFlow
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> Function UpdateEmail(NewEmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">8</span> LoggedInPage.OpenMyAccount()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">9</span> MyAccountPage.VerifyLoaded()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">10</span> MyAccountPage.EnterEmail(NewEmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">11</span> MyAccountPage.Save()</span><span style="font-family: unset;">
</span></span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="8559" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Now we can build out this version of the page object. </span></p><div class="code-block sc-exkUMo ghJhTx" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-radius: 3px; clear: both; display: grid; font-size: 16px; font-weight: 400; grid-template-columns: minmax(0px, 1fr); margin: 0.75rem 0px 0px; max-width: 100%; overflow-wrap: normal; padding: 0px; position: relative; tab-size: 4; white-space: pre-wrap;"><span class="sc-cBdUnI gmbcmM" style="-webkit-box-pack: end; display: flex; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; grid-column: 1 / auto; justify-content: flex-end; position: sticky; top: 0px;"><div role="presentation" style="margin: 0px; padding: 0px;"><div style="margin: 0px; padding: 0px;"><button aria-haspopup="true" aria-label="Copy" class="copy-to-clipboard css-1dgloit" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: #282a2a; --darkreader-inline-bgimage: none; --darkreader-inline-border-bottom: #3a3d3d; --darkreader-inline-border-left: #3a3d3d; --darkreader-inline-border-right: #3a3d3d; --darkreader-inline-border-top: #3a3d3d; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; -webkit-box-align: baseline; -webkit-box-pack: center; align-items: baseline; background: none rgb(244, 245, 247); border-color: rgb(255, 255, 255); border-radius: 4px; border-style: solid; cursor: pointer; display: flex; font-family: inherit; font-size: inherit; height: 32px; justify-content: center; line-height: 1.71429em; max-width: 100%; opacity: 0; outline: none; padding: 2px; position: absolute; right: 6px; top: 4px; transition: opacity 0.2s ease 0s; vertical-align: middle; white-space: nowrap; width: 32px;" tabindex="0" type="button"><span class="css-1ujqpe8" style="-webkit-box-flex: 0; align-self: center; display: flex; flex-grow: 0; flex-shrink: 0; font-size: 0px; line-height: 0; margin: 0px 2px; opacity: 1; transition: opacity 0.3s ease 0s; user-select: none;"><span aria-label="Copy" class="css-1w1m1we" role="img" style="display: inline-block; flex-shrink: 0; line-height: 1;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g data-darkreader-inline-fill="" fill="currentColor" style="--darkreader-inline-fill: currentColor;"></g></svg><span><path d="M10 19h8V8h-8v11zM8 7.992C8 6.892 8.902 6 10.009 6h7.982C19.101 6 20 6.893 20 7.992v11.016c0 1.1-.902 1.992-2.009 1.992H10.01A2.001 2.001 0 018 19.008V7.992z"></path><path d="M5 16V4.992C5 3.892 5.902 3 7.009 3H15v13H5zm2 0h8V5H7v11z"></path></span></span></span></span></button></div></div></span><span class="prismjs css-10fl9lg" data-code-lang="" data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-ds--code--code-block="" face="SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --darkreader-inline-bgimage: linear-gradient(to left, #282a2a 8px, rgba(13, 13, 13, 0) 8px), linear-gradient(to left, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px), linear-gradient(to right, rgba(22, 35, 58, 0.13) 0px, rgba(88, 96, 104, 0) 8px); background-attachment: local, scroll, scroll; background-color: var(--ds--code--bg-color,#F4F5F7); background-image: linear-gradient(to left, rgb(244, 245, 247) 8px, transparent 8px), linear-gradient(to left, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px), linear-gradient(to right, rgba(9, 30, 66, 0.13) 0px, rgba(99, 114, 130, 0) 8px); background-position: 100% 0px, 100% 0px, 0px 0px; border-radius: 3px; border-style: none; display: flex; font-size: 0.875rem; grid-column: 1 / auto; line-height: 1.5rem; overflow-x: auto; white-space: pre;"><code data-darkreader-inline-bgcolor="" data-darkreader-inline-bgimage="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-boxshadow="" data-darkreader-inline-color="" data-darkreader-inline-fill="" data-darkreader-inline-outline="" data-darkreader-inline-stopcolor="" data-darkreader-inline-stroke="" style="--darkreader-inline-bgcolor: unset; --darkreader-inline-bgimage: [object Promise]; --darkreader-inline-border-bottom: unset; --darkreader-inline-border-left: unset; --darkreader-inline-border-right: unset; --darkreader-inline-border-top: unset; --darkreader-inline-boxshadow: unset; --darkreader-inline-color: unset; --darkreader-inline-fill: unset; --darkreader-inline-outline: unset; --darkreader-inline-stopcolor: unset; --darkreader-inline-stroke: unset; -webkit-app-region: unset; -webkit-border-image: unset; -webkit-box-align: unset; -webkit-box-decoration-break: unset; -webkit-box-direction: unset; -webkit-box-flex: unset; -webkit-box-ordinal-group: unset; -webkit-box-orient: unset; -webkit-box-pack: unset; -webkit-box-reflect: unset; -webkit-font-smoothing: unset; -webkit-highlight: unset; -webkit-hyphenate-character: unset; -webkit-line-break: unset; -webkit-line-clamp: unset; -webkit-locale: unset; -webkit-mask-box-image: unset; -webkit-mask-composite: unset; -webkit-mask: unset; -webkit-perspective-origin-x: unset; -webkit-perspective-origin-y: unset; -webkit-print-color-adjust: unset; -webkit-rtl-ordering: unset; -webkit-ruby-position: unset; -webkit-tap-highlight-color: unset; -webkit-text-combine: unset; -webkit-text-decorations-in-effect: unset; -webkit-text-emphasis-position: unset; -webkit-text-emphasis: unset; -webkit-text-fill-color: unset; -webkit-text-orientation: unset; -webkit-text-security: unset; -webkit-text-stroke: unset; -webkit-transform-origin-x: unset; -webkit-transform-origin-y: unset; -webkit-transform-origin-z: unset; -webkit-user-drag: unset; -webkit-user-modify: unset; -webkit-writing-mode: unset; alignment-baseline: unset; animation: unset; appearance: unset; aspect-ratio: unset; backdrop-filter: unset; backface-visibility: unset; background-attachment: unset; background-blend-mode: unset; background-clip: unset; background-color: unset; background-image: linear-gradient(to right,var(--ds--code--line-number-bg-color,#EBECF0),var(--ds--code--line-number-bg-color,#EBECF0) calc(2ch + 16px),transparent calc(2ch + 16px),transparent); background-origin: unset; background-position: unset; background-repeat: unset; background-size: unset; baseline-shift: unset; block-size: unset; border-block: unset; border-collapse: unset; border-end-end-radius: unset; border-end-start-radius: unset; border-inline: unset; border-radius: unset; border-spacing: unset; border-start-end-radius: unset; border-start-start-radius: unset; border: unset; box-shadow: unset; box-sizing: unset; break-after: unset; break-before: unset; break-inside: unset; buffered-rendering: unset; caption-side: unset; caret-color: unset; clear: unset; clip-path: unset; clip-rule: unset; clip: unset; color-interpolation-filters: unset; color-interpolation: unset; color-rendering: unset; color-scheme: unset; column-fill: unset; column-rule: unset; column-span: unset; columns: unset; contain-intrinsic-size: unset; contain: unset; content-visibility: unset; content: unset; counter-increment: unset; counter-reset: unset; counter-set: unset; cursor: unset; cx: unset; cy: unset; d: unset; display: unset; dominant-baseline: unset; empty-cells: unset; fill-opacity: unset; fill-rule: unset; fill: unset; filter: unset; flex-flow: unset; flex: 1 0 auto; float: unset; flood-color: unset; flood-opacity: unset; font-feature-settings: unset; font-kerning: unset; font-optical-sizing: unset; font-size: unset; font-stretch: unset; font-style: unset; font-variant: unset; font-variation-settings: unset; font-weight: unset; forced-color-adjust: unset; gap: unset; grid-area: unset; grid: unset; height: unset; hyphens: unset; image-orientation: unset; image-rendering: unset; inline-size: unset; inset-block: unset; inset-inline: unset; inset: unset; isolation: unset; letter-spacing: unset; lighting-color: unset; line-break: unset; line-height: unset; list-style: unset; margin-block: unset; margin-inline: unset; margin: unset; marker: unset; mask-type: unset; mask: unset; max-block-size: unset; max-height: unset; max-inline-size: unset; max-width: unset; min-block-size: unset; min-height: unset; min-inline-size: unset; min-width: unset; mix-blend-mode: unset; object-fit: unset; object-position: unset; offset: unset; opacity: unset; order: unset; orphans: unset; outline-offset: unset; outline: unset; overflow-anchor: unset; overflow-clip-margin: unset; overflow-wrap: unset; overflow: unset; overscroll-behavior-block: unset; overscroll-behavior-inline: unset; overscroll-behavior: unset; padding-block: unset; padding-bottom: 8px; padding-inline: unset; padding-left: 0px; padding-right: 8px !important; padding-top: 8px; padding: 8px 8px 8px 0px; page-orientation: unset; page: unset; paint-order: unset; perspective-origin: unset; perspective: unset; place-content: unset; place-items: unset; place-self: unset; pointer-events: unset; position: unset; quotes: unset; r: unset; resize: unset; ruby-position: unset; rx: unset; ry: unset; scroll-behavior: unset; scroll-margin-block: unset; scroll-margin-inline: unset; scroll-margin: unset; scroll-padding-block: unset; scroll-padding-inline: unset; scroll-padding: unset; scroll-snap-align: unset; scroll-snap-stop: unset; scroll-snap-type: unset; shape-image-threshold: unset; shape-margin: unset; shape-outside: unset; shape-rendering: unset; size: unset; speak: unset; stop-color: unset; stop-opacity: unset; stroke-dasharray: unset; stroke-dashoffset: unset; stroke-linecap: unset; stroke-linejoin: unset; stroke-miterlimit: unset; stroke-opacity: unset; stroke-width: unset; stroke: unset; tab-size: unset; table-layout: unset; text-align-last: unset; text-align: unset; text-anchor: unset; text-combine-upright: unset; text-decoration-skip-ink: unset; text-decoration: unset; text-indent: unset; text-orientation: unset; text-overflow: unset; text-rendering: unset; text-shadow: unset; text-size-adjust: unset; text-transform: unset; text-underline-offset: unset; text-underline-position: unset; touch-action: unset; transform-box: unset; transform-origin: unset; transform-style: unset; transform: unset; transition: unset; user-select: unset; vector-effect: unset; vertical-align: unset; visibility: unset; widows: unset; width: unset; will-change: unset; word-break: unset; word-spacing: unset; writing-mode: unset; x: unset; y: unset; z-index: unset; zoom: unset;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #a9a093; color: #666666;"><span style="font-family: courier;"><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">1</span><span>Class LoginPage
</span><span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">2</span> UserNameSelector = "user-name-selector"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">3</span> PassWordSelector = "password-selector"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">4</span> LoginButtonSelector = "login-button-selector"
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">5</span>
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">6</span> function Login(self, EmailAddr, PassWord, TwoFactorAuth)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">7</span> LoginPage.OpenLoginPage()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">8</span> LoginPage.PopulateUser(EmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">9</span> LoginPage.PopulatePassword(PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">10</span> LoginPage.Submit()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">11</span>
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">12</span> function OpenLoginPage()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">13</span> Driver.OpenPage(globals.env_url)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">14</span> Driver.WaitForControlVisible(UserNameSelector, 20)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">15</span>
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">16</span> Function PopulateUser(EmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">17</span> Driver.SendKeys(UserNameSelector, EmailAddr)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">18</span>
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">19</span> Function PopulatePassword(PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">20</span> Driver.SendKeys(PassWordSelector, PassWord)
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">21</span>
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">22</span> Function Submit()
<span class="comment linenumber react-syntax-highlighter-line-number" data-darkreader-inline-color="" style="--darkreader-inline-color: #aaa194; box-sizing: border-box; display: inline-block; flex-shrink: 0; margin-right: 8px; min-width: 1.25em; padding-left: 8px; padding-right: 1em; text-align: right; user-select: none;">23</span> Driver.Click(LoginButtonSelector)</span><span style="font-family: unset;">
</span></span></code></span></div><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="9386" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Notice that this is all the same actual lines of code doing the work… But it’s broken up into many functions. We don’t actually NEED to break it up this much, login is a fairly simple process… But imagine that we may also need to test login failures in the future. So we can’t only have the one wrapper function. We might need to <code class="code css-9z42f9" data-darkreader-inline-bgcolor="" data-renderer-mark="true" style="--darkreader-inline-bgcolor: var(--darkreader-bg--ds--code--bg-color, #282a2a); --ds--code--bg-color: rgba(9,30,66,0.08); -webkit-box-decoration-break: clone; background-color: var(--ds--code--bg-color,#F4F5F7); border-radius: 3px; border-style: none; display: inline; font-family: SFMono-Medium, "SF Mono", "Segoe UI Mono", "Roboto Mono", "Ubuntu Mono", Menlo, Consolas, Courier, monospace; font-size: 0.875em; overflow-wrap: break-word; overflow: auto; padding: 2px 0.5ch 2px 0.5ch;">Submit()</code>, and then verify an error message, for instance.</span></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="9770" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Now we can start debugging it. Because of course it won’t work first try. <span data-emoji-id="1f642" data-emoji-short-name=":slight_smile:" data-emoji-text="🙂"><span aria-label=":slight_smile:" class="f1yhv2qy emoji-common-node" style="display: inline-block; margin: -1px 0px;"><span role="presentation"><span class="emoji-common-emoji-sprite" data-darkreader-inline-bgcolor="" style="--darkreader-inline-bgcolor: transparent; background-attachment: initial; background-clip: initial; background-color: transparent; background-origin: initial; background-position: 30.5556% 0%; background-repeat: no-repeat; background-size: 3700% 3600%; background: url("https://pf-emoji-service--cdn.us-east-1.prod.public.atl-paas.net/standard/a51a7674-8d5d-4495-a2d2-a67c090f5c3b/64x64/spritesheets/people.png") 30.5556% 0% / 3700% 3600% no-repeat transparent; display: inline-block; height: 20px; vertical-align: middle; width: 20px;"> </span></span></span></span> </span></p></h4><h2 data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-color="" data-renderer-start-pos="9848" id="The-Layers-We-Just-Built" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-border-bottom: #484b4b; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; border-bottom-color: rgb(204, 204, 204); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 1.43em; font-weight: normal; letter-spacing: -0.008em; line-height: 1.2; margin: 1.8em 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">The Layers We Just Built</span><span class="heading-anchor-wrapper" role="presentation" style="height: 1.2em; margin-left: 6px; position: absolute;"><button class="sc-hzDkRC dsymMa" data-darkreader-inline-bgcolor="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left="" data-darkreader-inline-border-right="" data-darkreader-inline-border-top="" data-darkreader-inline-color="" data-darkreader-inline-outline="" style="--darkreader-inline-bgcolor: transparent; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-top: initial; --darkreader-inline-color: #a2afba; --darkreader-inline-outline: initial; background-color: transparent; border-color: initial; border-style: none; border-width: initial; cursor: pointer; display: inline; font-family: inherit; opacity: 0; outline: none; padding-left: 0px; padding-right: 0px; right: 0px; transform: translate(-8px, 0px); transition: opacity 0.2s ease 0s, transform 0.2s ease 0s;"><span aria-label="copy" class="css-1i2mldy" data-darkreader-inline-color="" role="img" style="--darkreader-inline-color: #e5e0d8; color: white; display: inline-block; flex-shrink: 0; height: 24px; line-height: 1; width: 24px;"><svg height="24" role="presentation" viewbox="0 0 24 24" width="24"><g fill-rule="evenodd" fill="currentColor"></g></svg><span><path d="M12.856 5.457l-.937.92a1.002 1.002 0 000 1.437 1.047 1.047 0 001.463 0l.984-.966c.967-.95 2.542-1.135 3.602-.288a2.54 2.54 0 01.203 3.81l-2.903 2.852a2.646 2.646 0 01-3.696 0l-1.11-1.09L9 13.57l1.108 1.089c1.822 1.788 4.802 1.788 6.622 0l2.905-2.852a4.558 4.558 0 00-.357-6.82c-1.893-1.517-4.695-1.226-6.422.47"></path><path d="M11.144 19.543l.937-.92a1.002 1.002 0 000-1.437 1.047 1.047 0 00-1.462 0l-.985.966c-.967.95-2.542 1.135-3.602.288a2.54 2.54 0 01-.203-3.81l2.903-2.852a2.646 2.646 0 013.696 0l1.11 1.09L15 11.43l-1.108-1.089c-1.822-1.788-4.802-1.788-6.622 0l-2.905 2.852a4.558 4.558 0 00.357 6.82c1.893 1.517 4.695 1.226 6.422-.47"></path></span></span></button></span></h2><h4><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="9874" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">We’ve just built a stack with 3 layers:</span></p><ul class="ak-ul" data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-indent-level="1" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; box-sizing: border-box; display: flow-root; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 0px; padding: 0px 0px 0px 24px; white-space: pre-wrap;"><li><p data-renderer-start-pos="9917" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Tests – Tests know about <strong data-renderer-mark="true">data</strong>, and about which flow objects to ask to do it’s bidding. Tests know nothing at all about pages ever. Tests can talk to flows, and that’s it. Tests can also validate data that comes back to them from the flows.</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="10160" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Flows – Flows know <strong data-renderer-mark="true">which pages</strong> do what, and <strong data-renderer-mark="true">what to ask them to do</strong>. But they don’t validate data. Flows can call pages and other flows as well. Flows can also manipulate data, but this should be kept to a minimum. A flow may need to reformat data from one page object call before passing it to a different page in a later step.</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="10491" style="font-size: 1em; letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;">Pages – Pages know about the controls on their page, and how to get it all to do what it does. Pages NEVER call other pages, flows or tests. Pages can pass data back to the flows.</span></p></li></ul><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="10674" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;"><span></span></span></p><div class="separator" style="clear: both; text-align: center;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;"><span><a href="https://lh3.googleusercontent.com/-c9teWJvtWtI/YO2zWLVtl7I/AAAAAAAAboo/lIHIWYAenZQVRrp8mmpuVXagUpFKxUEHQCLcBGAsYHQ/image.png" style="margin-left: 1em; margin-right: 1em;"><img data-original-height="639" data-original-width="1209" height="228" src="https://lh3.googleusercontent.com/-c9teWJvtWtI/YO2zWLVtl7I/AAAAAAAAboo/lIHIWYAenZQVRrp8mmpuVXagUpFKxUEHQCLcBGAsYHQ/w400-h228/image.png" width="400" /></a></span></span></div><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;"><span><br /><br /></span></span><p></p><p data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" data-renderer-start-pos="10674" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #c7c1b6; -webkit-text-stroke-width: 0px; background-color: black; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; font-size: 16px; font-weight: 400; letter-spacing: -0.005em; line-height: 1.714; margin: 0.75rem 0px 0px; padding: 0px; white-space: pre-wrap;"><span data-darkreader-inline-color="" style="--darkreader-inline-color: #e5e0d8; color: white;"><span>(There’s a lot more than that which we’re leaving out, </span><span>like the layer that is the browser, and the layer that is the testing tool, and so on.) </span></span></p></h4>Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-63308535805719260132021-07-02T15:11:00.012-07:002021-07-05T18:26:20.100-07:00SQA: Our Tasking<ul class="ak-ul" data-indent-level="1" style="box-sizing: border-box; display: flow-root; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif; margin: 0px; padding: 0px 0px 0px 24px; text-align: left; white-space: pre-wrap;"><li><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" face="-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, "Droid Sans", "Helvetica Neue", sans-serif" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white; letter-spacing: -0.005em; white-space: pre-wrap;">We have to produce and maintain more tests than is physically possible. Obviously this is not an attainable goal, so our attainable goal is simply to automate as much as possible with the time we have, with sensitivity to both the business and dev priorities, and to do that consistently (which means not killing ourselves in the process).</span></li><li style="margin-top: 4px;"><p data-renderer-start-pos="346" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">What is our remit if our role is Quality Assurance Engineer?</span></p><ul class="ak-ul" data-indent-level="2" style="box-sizing: border-box; display: flow-root; margin: 0px; padding: 0px 0px 0px 24px;"><li><p data-renderer-start-pos="410" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;"><span>Define tests which will prove the positive cases defined in the requirements/user stories/etc.</span><span> </span><span style="font-size: 1em;">
</span><span style="font-size: xx-small;">(Getting the correct error message on invalid input is still a positive case for the purposes of this discussion.)</span></span></p><ul class="ak-ul" data-indent-level="3" style="box-sizing: border-box; display: flow-root; margin: 0px; padding: 0px 0px 0px 24px;"><li><p data-renderer-start-pos="507" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Absent that, we make wild guesses at what the System Under Test is supposed to do, and hold it to that until told better.</span></p></li></ul></li><li style="margin-top: 4px;"><p data-renderer-start-pos="634" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Document those tests (and advocate for suitable tools if they are not available, and no, Excel doesn’t count)</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="747" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Execute those tests and deliver bugs for requirements not met</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="812" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Think of the things the developer didn’t</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="856" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">User advocate</span></p></li></ul></li><li style="margin-top: 4px;"><p data-renderer-start-pos="875" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">What is our remit if our role is Automation Engineer?</span></p><ul class="ak-ul" data-indent-level="2" style="box-sizing: border-box; display: flow-root; margin: 0px; padding: 0px 0px 0px 24px;"><li><p data-renderer-start-pos="932" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Deliver tests which will prove the positives</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="980" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Implement those tests in a way where maintenance can be minimized and stability maximized</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1073" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Document the code units that implement those tests such that the whole team can use them</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1165" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We report on defects found in automation <em data-renderer-mark="true">AND FLAG THEM AS FOUND IN AUTOMATION</em>.</span></p></li></ul></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1249" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">What is our remit if our role is Automation Architect?</span></p><ul class="ak-ul" data-indent-level="2" style="box-sizing: border-box; display: flow-root; margin: 0px; padding: 0px 0px 0px 24px;"><li><p data-renderer-start-pos="1307" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;"><em data-renderer-mark="true"><strong data-renderer-mark="true">Consistently </strong></em>deliver build go/no go decisions</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1356" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Define the standards by which the tests and their execution environment(s) will be built and maintained such that:</span></p><ul class="ak-ul" data-indent-level="3" style="box-sizing: border-box; display: flow-root; margin: 0px; padding: 0px 0px 0px 24px;"><li><p data-renderer-start-pos="1474" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We maximize reuse</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1495" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We maximize stability and uptime</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1531" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We maximize the access to information</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1572" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We minimize the time to bring new people up to speed</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1628" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We minimize the time required to identify and isolate issues</span></p></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1692" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We minimize the time required to look stuff up during test development and debugging</span></p></li></ul></li><li style="margin-top: 4px;"><p data-renderer-start-pos="1782" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">We document, design, implement, delegate, train, validate, source expertise and otherwise do what it takes to make sure that our team can do all of the above</span></p><ul class="ak-ul" data-indent-level="3" style="box-sizing: border-box; display: flow-root; margin: 0px; padding: 0px 0px 0px 24px;"><li><p data-renderer-start-pos="1943" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">In particular, we want to be training our replacement(s) from day one. We are responsible for assuring success even if we’re no longer available.</span></p></li></ul></li><li style="margin-top: 4px;"><p data-renderer-start-pos="2094" style="letter-spacing: -0.005em; line-height: 1.714; margin: 0px; padding: 0px;"><span data-darkreader-inline-bgcolor="" data-darkreader-inline-color="" style="--darkreader-inline-bgcolor: #0d0d0d; --darkreader-inline-color: #e5e0d8; background-color: black; color: white;">Communicate with other teams about our needs, accomplishments, test results, RPA, automation training, and anything else we can provide.</span></p></li></ul></li></ul>Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-29812529251893432312020-12-21T12:17:00.002-08:002020-12-21T12:17:45.444-08:00SDET vs Architect?<p> I had somebody asked me the other day what the difference was between a test automation architect and an SDET.</p><p>An SDET is a software developer in test. Software developer skillset applied to testing. I'm glad this now has a name and certification. When I started, there wasn't such.</p><p>An Architect is any developer who's got enough miles under their belt that they look at everything from a the perspective of how it all fits together and interacts with everything outside of itself.</p><p>Can an SDET be an architect? Absolutely. Are all SDETs experientially equipped to approach their work as Architects? No more so than all developers are architects.</p><p>I started working with computers in 1979, I got my first job in computers in 1982, got my first support gig in 1987. my first QA gig in 1990. First automation assignment in dec 1990. </p><p>A year later, and I was overwhelmed by the maintenance. It was a month after I realized that fact that I started thinking about test automation from the perspective of architecture.</p><p>My own interest came first from trying to solve my own problems, and then from those my management asked of me. How can I reuse most of what I produce? How can I reduce maintenance? Can you build out a lab so we can run these over and over? I want you to make it so everybody in QA can create automated tests, even the non programmers. We have too much data left over from all those old builds and test runs. </p><p>I've solved every problem thrown at me. I'm a good tester, but I'm a much better architect.</p>Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-39871002054332897062020-06-20T21:28:00.002-07:002020-06-30T12:33:39.105-07:00<h3>
Selenium As A Modern Automation Tool</h3>
2020 Akien Maciain, Paul Castillo, Alejandro Garcia<br />
<br />
<h3>
Introduction </h3>
This document covers a deployment of Selenium using Python for a large retail firm. It was started in 2015, and as of this writing is still in use today. Here we provide an overview of key features. A companion file includes a functional mockup in Python 3, which is not taken from the firm's code.<br />
<br />
The systems we were testing were customer and retail associate facing pages responsible for connecting consumers with home service professionals who would perform services such as house painting, plumbing installation and repair, and so on.<br />
<br />
<h3>
Terms</h3>
<ul>
<li>Control - A proxy object that represents a control on the screen.</li>
<li>Element - A DOM object on the screen that a "control" interacted with.</li>
<li>Golden Path - A flow that a user might perform that is without errors, backtracking, or other tangents.</li>
<li>SUT - System Under Test</li>
</ul>
<br />
<h3>
Primary features</h3>
<ul>
<li>Fully Automatic Self Synchronization for all control interaction, with interfaces loosely based on tools like UFT and SilkTest. Zero sync failures.</li>
<li>Storyboarding is implemented as instances of a"flow class", an object layer between the tests and page objects. These abstract a flow a user might do. Adding this layer made the tests far more resilient.</li>
<li>Tagged Interfaces that prevented the need for maintenance in most cases. This required buy in from development, but became the channel that developers used to communicate UI changes to QA. Almost as good as AI-based self healing.</li>
<li>Engine based testing that validates all controls whether the automation "engages with them" or not.</li>
<li>Fully automated self setup for all environments, including a Docker container to actually run the tests to reduce the differences between individual computers.</li>
</ul>
<br />
<h3>
Fully Automatic Self Synchronization</h3>
The framework as implemented did not have issues with synchronizing with the SUT. This was accomplished by implementing the controls as objects, with instances on the page objects for each control. When the controls are instantiated, the selector is one of the passed arguments.<br />
<br />
The system performs automatic retries until a desired condition is met or timeouts are reached on all interactions with the page elements. This prevents stale element exception failures and waiting even one millisecond longer than is needed.<br />
<br />
Asserting states before actions (eg asserting that a page is loaded before trying to interact with it) means the test only stops when it can't continue. Other failures that don't block test completion are logged as the test continues.<br />
<br />
<h3>
Storyboarding via Flow Layer</h3>
A flow is any activity a user might do. It often spans more than one page.<br />
<br />
The test knows which flows to call but does not know anything about pages or controls. This decoupling of tests and pages means even in the face of a broad revision to all the page code the tests are unchanged. Pages are rebuilt and flows updated to point to the new pages. As long as the underlying application features still do basically the same things, the tests are unaffected.<br />
<br />
In our model, tests are responsible for data, which is passed into flows as a hash table. Flows knew which page object and methods to use and passed the dictionary along. Pages implement page-specific subflows as methods and which controls handle what actions.<br />
<br />
Strict encapsulation meant very low maintenance even in the face of big change.<br />
<br />
<h3>
Tagged Interfaces</h3>
In this framework, we could specify control selectors in css at instantiation time. Usually those css tags were not native products of the front end framework, but rather "selenium tags", like so:<br />
<br />
<h3 selenium="tile-name" class="TILE-NAME">Bath</h3><br />
<br />
It took some convincing some developers to add these tags. We'd create tasks for them to implement these tags, and then we'd instantiate a control like this:<br />
<br />
tile_header = Control(selector='[selenium="tile-name"]')<br />
<br />
When the dev team rearranged a page, they would move the tags to the new controls. If they failed to do so, we'd write a blocking bug, and they'd get it fixed straight away.<br />
<br />
Eventually, Dev trained the Quality Assurance teams to make these changes to the front end code ourselves.<br />
<br />
Tagged interfaces are theoretically not as robust as Artificial Intelligence "self healing" features of many of the tools now on the market. However, on the ground, this proved entirely adequate.<br />
<br />
<h3>
Engine Based Testing</h3>
In the normal course of automation, we would touch only specific controls. For instance, in a login window, we would touch the username, password, and submit button. This limits the coverage of the automation to what we called "key controls".<br />
<br />
However, a page is made up of much more than the key controls. Many have common objects such as headers, footers, hero images, title text, and so on. While not critical to the golden path, these objects can all be extremely important to validate.<br />
<br />
To deal with this, we implemented an engine. When we instantiated the control objects, we would also add attributes that would tell the engine how to test the control. So if a control should exist, it might have a VERIFY_EXIST=True as one of it's instantiation arguments. We could verify existence or not, visible or not, enabled or not, whether a control was unique, what it's expected text value was, and so on.<br />
<br />
We also implemented KEY as one of our instantiation keywords, and the KEY pointed to a keyed value in the test data dictionary. So we could deal with dynamic values as well.<br />
A flow would simply call the engine before doing anything else on the page. For some golden path tests we could even populate fields like username and password using the KEY value, and not even have to write a subflow for that page.<br />
<br />
<h3>
Self Setup</h3>
One of the features that was mentioned repeatedly with appreciation by the team was the fully automated setup. A person could download a script from the knowledge base, run it locally, and it would set up everything about the automation. All system packages, download all the needed repos from github, set up their docker container… Everything. This was implemented in Bash, but today we'd use something like Ansible.<br />
<br />
We used Docker to provide a single, consistent environment for the testing tools and execution environment, which meant that the specifics of how individual computers didn't impact the testing.<br />
<br />
<h3>
Conclusion</h3>
After having conducted a thorough high level overview of many of the tools on the market, we were amused to see how many claimed these features could not be implemented in Selenium and expensive tools were instead required in order to make maintainable tests. This framework proves that assertion wrong. However, it does require building out the control object, getting dev buy-in for tags, and implementation of an Ansible (or similar) setup script. None of this is beyond the capabilities of a competent engineer.<br />
<br />Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-64886354157087172632019-02-05T20:56:00.000-08:002020-03-10T20:57:31.626-07:00Automation Pain Points II: Resilience Part 3, SelectorsIn the last two sections, we looked at strategies that orchestrate the behavior of pages and flows. These have made a huge difference in our day to day work. The third piece requires buy in from dev.<br />
<br />
We kept rebuilding page objects, but sorting the new selectors was kind of a nightmare. Just when we thought it was all done, changed again!<br />
<br />
We realized that the developer still knows where the first name field is... Or the submit button... Or whatever.<br />
<br />
So one of our engineers made this case to dev: We will add selectors to our pages as we normally do. We will also open a ticket to have new properties added to each control. The property name will be 'selenium', so we can have selectors like '[selenium="first-name"]'.<br />
<br />
The engineer is responsible for adding those tags. And when the page is changed, they're the ones changing it, and they are required to keep the selenium properties intact as they redesign the page. If a selenium tag disappears, it's a P0 bug, because it blocks testing.<br />
<br />
They are not required to put selenium tags on anything we didn't identify as needing them.<br />
<br />
This took us from a couple of days before everything was smooth again to a few minutes. Huge benefit!Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-55242105199305101382018-11-29T16:27:00.000-08:002018-11-29T17:16:35.472-08:00Automation Pain Points II: Resilience Part 2, FlowsIn the last piece, we looked at the benefits I've seen at other companies using strict encapsulation at the page level. But in my current gig, we take it a step farther. We have another layer between tests and pages called <span style="font-family: "courier new" , "courier" , monospace;">flows</span>. A flow interacts with one or more page objects.<br />
<br />
This means that pages are the workers, they know how to do specific pieces of work.<br />
<br />
Flows are the managers, they line up which workers and in what order. Flows are also a series of interactions the user will take to perform some action. They model the end user's interaction with the system.<br />
<br />
The tests are the executives that decide which flows to call to test a specific underlying feature or group of features.<br />
<br />
In practice, this looks like a lot of unnecessary complexity, especially for test cases who's flows are one line long. But it also further isolates the test from the changes in the UI.<br />
<br />
We've had large scale redesigns that mostly take page object updates. Sometimes flows need to be updated. But the underlying functionality hasn't really changed.<br />
<br />
This means tests are updated more quickly, and so the automation can do more for less effort.<br />
<br />
It also means we can write automated tests and flows before we have page mockups, as long as we have detailed enough specs. We can add the page objects as they become available.<br />
<br />
Woo hoo!Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-17933100005660363322018-11-03T12:27:00.000-07:002018-11-29T17:16:22.321-08:00Automation Pain Points II: Resilience Part 1, The PageI am a fundamentally lazy guy. That's why I write test automation, because I don't want to keep doing the same crap over and over.<br />
<br />
But the problem with encoding knowledge into source code is that the representation of knowing thus created is brittle. Every time the AUT changes, the tests have to change.<br />
<br />
I saw this first at Symantec in 1991, working on OnTarget, a project management package. There were 3 of us automators, and we built up a rather small lot of tests, before we became unable to create any more. We were too busy keeping up with AUT changes.<br />
<br />
By this time, we were using SilkTest, and their implementation of page objects. Their page objects were really just intended to be a better tool for managing selectors than the huge lists of constants that was the way QAWorkbench (which became SilkTest) had done in the alpha stage of it's development.<br />
<br />
But we took it a step further, and built what folks today would recognize as page objects. The point behind the way we use page objects at Home Depot today is to encapsulate all the details of interacting with a page into one place.<br />
<br />
When I started there, we had tests directly calling methods in page objects. So when they completely redesigned the app, all the tests had to be thrown out. This is not good resilience.<br />
<br />
The problem was that the test code was tightly coupled to the page code. The rule when I arrived was that no test used the selectors on a page. Instead, methods were created. I understand why they created that rule, but the problem was they had routines like click_submit_button(), which meant that how to operate the page was encoded in the test. The AUT underwent a complete redesign, and all the tests were then trash, and had to be rebuilt almost completely from scratch.<br />
<br />
Today, each page object implements subflows. A subflow takes a dictionary with all the data needed for that page, and implements all the steps for a single action on that page. Everything about how the page does it's work is encapsulated and isolated from the test. Even default values we expect controls to have, such as the text of error messages that should appear, are stored in the page object itself.Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-82943933470850674712018-10-29T20:51:00.000-07:002018-10-29T20:51:45.344-07:00Automation Pain Points I: SynchronizationLet me say first that I love what I can do with test automation. It has definitely become an art over the years I've been doing it.<br />
<br />
One of the very first pain points I encountered was with synchronization. In those days, I had to build my own tools. This is just before SilkTest went Beta 1. I tried sleeps. They worked poorly.<br />
<br />
And when I started at Home Depot, I got into quite the argument about using sleep statements. A co-worker, Clay, got bent at me because I had put a sleep of 1/3 second into a routine.<br />
<br />
Now, mind you, what this routine did was to poll for multiple different controls, every third of a second, until one of several conditions were met. He insisted "no sleep statements!". So I took him for a walk inside of webdriver, where it does the exact same thing. It was a good example of somebody following a rule because there's a rule, not because it was warranted in that case.<br />
<br />
Sleep statements are not a good solution in test automation, mostly ever. My condition was unusual because there was no way to do all these checks at the same time otherwise.<br />
<br />
But when anybody puts a sleep in a test otherwise, I call them on it. I know just how bad careless use of sleep statements can be. I have made tests take longer than necessary by trying to extend a wait to cover all the various response times from the application under test (AUT).<br />
<br />
So instead, I ask folks to look at "how do you as a human know the AUT is ready to continue?" Is it because a field has become populated? We have an <span style="font-family: "courier new" , "courier" , monospace;">assert_not_empty()</span> to cover that condition. Is it because a control exists? We have <span style="font-family: "courier new" , "courier" , monospace;">assert_exist()</span> to cover that condition. Are we waiting for it to have a specific value? We have <span style="font-family: "courier new" , "courier" , monospace;">assert_value()</span> to cover that one. All of these validation routines take a timeout as an argument.<br />
<br />
I don't believe in rules for their own sake. In fact, I think the fewer rules we have, the faster development goes. Everything about our framework is about velocity. Reducing the time to build and maintain tests. Don't use sleep except in the most unusual of circumstances is one I do keep.<br />
<br />Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-12762855898165682462018-10-29T08:11:00.001-07:002018-10-29T08:11:43.255-07:00Keeping Your PerspectiveFor those of us who've been in IT for many years, the number of new and exciting technologies, paradigms, methodologies and philosophies around us right now can seem overwhelming. Ideas that started in small shops, startups and incubators are now reaching in to even the most conservative of industries. Even my own industry, dental insurance, is starting to adopt agile practices.<br />
<br />
It can be difficult to find your way through this barrage of new ideas: We want continuous integration and continuous delivery and your framework needs to support our particular flavour of agile but it also needs to support our legacy waterfall apps but we're not going to call them waterfall any more and also we need better reporting and traceability and and and ...<br />
<br />
At times like this, I find it very helpful to take a step back and re-orient myself around a single, simple tenant:<br />
My job, as automation engineer, is to support quality.<br />
<br />
Let's unpack that a little. Quality means different things to different people and organizations. You might measure quality with defect metrics, you might have some sort of federally mandated guidelines, you hopefully have a set of functional and non-functional requirements your are gauging against. Regardless of how you measure it, your job, as an automation engineer, is to do everything you can to help ensure quality.<br />
<br />
Functionally, test automation is a component of overall QA. If your core QA practices are shaky, the best automation in the world will not save you. Everything you do as an automation engineer need to ultimately serve to bolster QA. Whether you are part of a small team or have an impact across the enterprise, this holds true.<br />
<br />
I use this tenant every day. We are in the midst of developing a new enterprise-wide automation framework. Keeping my perspective on "support quality" helps me filter through the options when choosing technologies and methodologies. It helps me to remember who the stakeholders are when I'm designing elements of the framework, such as reporting. It helps me figure out the how when tasked with something like adding testing to our CI setup. Hopefully it can help you too.Chadhttp://www.blogger.com/profile/00460612750248637666noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-2434109623955255732018-10-21T13:49:00.002-07:002018-10-29T19:46:33.549-07:00The Pain Of Test Automation"So what do YOU think are the biggest pain points for test automation?" Someone asked me. I've been thinking about that for the last several weeks. This is the list I came up with:<br />
<br />
Synchronization - Making sure the test doesn't get ahead of itself, and making sure that it can continue.<br />
<br />
Resilience - Recovering quickly after the application under test changes.<br />
<br />
Interface Proliferation - When test automation libraries get too big.<br />
<br />
Networking Problems - Possibly my number one issue at my current gig.<br />
<br />
Looking Stuff Up - How much time is spent looking up how to do things. A lot more than most people think.<br />
<br />
Data Management - Getting consistent data into the application under test, mocking external interfaces, and so on.<br />
<br />
Artifact Aging - What test artifacts to hold on to and for how long.<br />
<br />
Reading and Maintaining Other People's Code - Coding standards, training, and so on.<br />
<br />
The next few posts will look at each of those in a little more depth, including how I deal with that in my current work.<br />
<br />
Stay tuned!Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-76798631039983417342017-06-06T14:39:00.003-07:002017-06-06T14:39:45.006-07:00An Oddity of Historyi was watching crash course computer science on youtube. it's been kind of amusing, i have learned a few things, but there just isn't much i didn't already know... with the episodes shown so far, anyway.<br />
<br />
but i ran across the example below, which was illustrating the 'else' clause:<br />
<br />
if x then<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>print a<br />
else<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>print b<br />
<br />
and i had this weird 'wait a minute, i often don't do that in that way!'... at first, i briefly had a judgement that my way was better. when i asked myself why i thought this way was better, my cpu came back with a shrug.<br />
<br />
this is the kind of structure i have historically often used:<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span><br />
r = a<br />
if x then<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span>r = b<br />
print r<br />
<br />
and as i sat there scratching my head, i realized that long ago, msdos 2.1 batch files didn't support else. in fact, the modern cmd.exe command set does support else, though i wasn't able to track down when that was added (or if it had been there all along and just poorly documented).<br />
<br />
my approach requires extra memory. when you put all the commands on the same line, mine is slightly shorter. i don't think either of these differences qualifies as better. and i guess one of the marks of a good engineer is one that doesn't hold onto the old way of doing it just because that's what they learned. yay for having dispelled an obsolete assumption!Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-25232011745697669482017-01-03T13:23:00.002-08:002017-01-05T11:45:57.765-08:00Libraries and API designSome time back, I ran across this: http://www.softwaretestinghelp.com/test-automation-frameworks-selenium-tutorial-20/<br />
<br />
And I was taken aback at the description of the framework, as he was ignoring the parts of framework development that seemed to me to be the most important parts.<br />
<br />
Consider, if we use just Selenium and write our tests to it, then everybody we hire who knows selenium (and our target language) will be able to write automated tests. We will probably set up page objects, and that's about the minimal set of things to know. Right?<br />
<br />
Well, yes, but...<br />
<br />
From my experience, many QA organizations would prefer to be able to graduate successful testers into at least some automation. This often means training less experienced testers to write automation.<br />
<br />
And if your framework is more complex than the above (and most of them are), then there's going to be some kind of a learning curve, even for more experienced automators.<br />
<br />
My job as an architect is to 'design and maintain the framework'. And how well I do that design part will directly influence how productive the people who use that framework will be.<br />
<br />
At my first automation gig, there were 3 of us, and we all were pretty good programmers, so we just started banging out tests. We had a set of shared page objects, and wrote any other functions that helped us out as we needed them.<br />
<br />
This meant that, at different times, we each wrote something to remove all non numeric characters from a string. Because we happened to need that, and because there was no orchestration of our work. No good standards for code sharing, no limitations on what we could create. Doh!<br />
<br />
At one point, I even ran into a test I'd written a month earlier that implemented some function or other (I no longer remember what) that I'd just implemented over again because I'd forgotten that it existed. My own code!<br />
<br />
This was when I learned about interface proliferation. The tendency of good programmers to try and reuse code had led to more interface endpoints than we could keep track of.<br />
<br />
From then on, I started looking at test automation interface design as it's own discipline. And not only the design of the interfaces, but how I communicate about them as well. What documentation I create and where.<br />
<br />
These two pages have strongly influenced my API designs:<br />
<br />
http://martinfowler.com/bliki/HumaneInterface.html<br />
http://martinfowler.com/bliki/MinimalInterface.html<br />
<br />
Now, I try to create minimal interfaces, and the interfaces I do create require the bare minimum of data to do the most common action. So for instance, when I started in my current gig, they had created a function called 'does_control_exist(selector)' to wrap the fact that selenium has no exist function. They also had a 'wait_for_control_to_exist(selector, time)'. Two interfaces to determine existence. What if it was just .exist([time])? The [time] is in brackets because it's optional. Two calls compressed into one.<br />
<br />
It's also the case that when I used our framework's click(), I'd often get 'control not found' if the code tried to click on something that existed more than once. And there was nothing to tell me if a control was unique or not.<br />
<br />
Now I have .click([time]), so a call might look like thingie.click(), which does the following:<br />
<br />
since no timeout was provided, use 20 seconds<br />
get the element for the selector<br />
assert that the item exists<br />
assert that the item is unique<br />
assert that the item is visible<br />
assert that the item is enabled<br />
element.click()<br />
<br />
And that happens each time we click on something. This is how most test automation tools worked before selenium.<br />
<br />
So my page object may look something like this:<br />
<span style="font-size: x-small;"><span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">class PageHome(MyPageClass):</span></span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> complete_page_button = ButtonClass("css=[id='Next-Button']")</span><br />
<span style="font-size: x-small;"><span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;"> def complete_page(self):</span></span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if self. complete_page_button.exist() is False:</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> self.invoke_page()</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> self.complete_page_button.click()</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> ...</span><br />
<br />
So for exist, we now have one function instead of 2. For click, we've collapsed at least 6 actions into one function. This means my test automation engineers have fewer end points to remember, and for the most common use of an endpoint, no additional parameters are required. I can get new people up to speed faster, and we can debug failed tests more quickly because we get more useful feedback in the logs.<br />
<br />
For me, this is what framework design is all about.<br />
<br />Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-10673184653891140152016-10-12T12:05:00.002-07:002016-10-13T17:03:11.014-07:00Python and how to get the instance name of a variableI needed the name of an instance in order to do some error reporting. I'm very lucky that each of these things I need to report on will only have one instance.<br />
<br />
So of course, first thing I did was ask Google. And after a couple of weeks of checking now and again, I have been unable to find any solutions already published. I found a lot of "instances don't have names!", which clearly isn't true, but makes sense from the perspective that the instance name is a pointer to the thing, and there's no backwards pointing property in the thing being pointed at.<br />
<br />
But as with most things, there's always a way. And it turns out that in python, it's actually pretty straightforward:<br />
<br />
<pre style="background-color: #2b2b2b; color: #a9b7c6; font-family: 'Menlo'; font-size: 9.0pt;"><span style="color: #cc7832; font-weight: bold;">import </span>gc
<pre style="font-family: Menlo; font-size: 9pt;"><span style="color: #cc7832; font-weight: bold;">def </span><span style="font-weight: bold;">instance_names</span>(<span style="color: #94558d;">self</span>):
<pre style="font-family: Menlo; font-size: 9pt;"> referrers = gc.get_referrers(<span style="color: #94558d;">self</span>)
result = []
dict_of_things = {}
<span style="color: #cc7832; font-weight: bold;"> for </span>item <span style="color: #cc7832; font-weight: bold;">in </span>referrers:
<span style="color: #cc7832; font-weight: bold;">if </span><span style="color: #8888c6;">isinstance</span>(item<span style="color: #cc7832;">, </span><span style="color: #8888c6;">dict</span>):
dict_of_things = item
<span style="color: #cc7832; font-weight: bold;"> for </span>k<span style="color: #cc7832;">, </span>v <span style="color: #cc7832; font-weight: bold;">in </span>dict_of_things.items():
<span style="color: #cc7832; font-weight: bold;">if </span>v == <span style="color: #94558d;">self</span>:
result.append(k)
<span style="color: #cc7832; font-weight: bold;"> if not </span>result:
result = [<span style="color: #a5c261;">'unnamed instance'</span>]
<span style="color: #cc7832; font-weight: bold;"> return </span>result</pre>
</pre>
</pre>
<br />
Returns a list of all matching instance names. Now it's possible to create an instance without a name, such as via a generator or a lambda, and I haven't tested what it will return in those cases, as that wasn't what I needed this for. It also fails for getting the name of a function, but that can gotten in other ways.<br />
<br />
I hope this helps someone! :)<br />
<br />Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-65081753512390708192016-10-12T11:32:00.003-07:002016-10-14T15:22:13.231-07:00Python, Selenium and the dreaded "Timed out receiving message from renderer"We had a problem, around 15% of the time, we were getting tests that failed, and when we went to SauceLabs to look at the problem, we just had a blank browser page with "data;" in the address field.<br />
<br />
Folks here had been ignoring this and had just pronounced the automated tests to be flaky and unreliable. Having the experience I have, this was galling.<br />
<br />
I looked in the selenium output, and saw "Timed out receiving message from renderer".<br />
<br />
So I looked on Google, and found lots of folks reported having this problem. Several bugs have been written, all closed with "can't duplicate". This issue is at least 4 years old as of this writing. I tried changing timeouts, using try/except, and every other thing listed, but improvements were either nonexistent or very modest.<br />
<br />
I have solved it, but the solution is an *awful* hack. The one thing it has going for it, our failures have dropped from 15% to 0. (Testing done using a suite with 10,000 cases in it.)<br />
<br />
Here's the code at the center of the solution:<br />
<br />
<pre style="background-color: #2b2b2b; color: #a9b7c6; font-family: 'Menlo'; font-size: 9.0pt;">webdriver.get(<span style="color: #a5c261;">'about://blank'</span>)
my_script = <span style="color: #a5c261;">'var a = document.createElement("a");' </span>\
<span style="color: #a5c261;">'var linkText = document.createTextNode("%s");' </span>\
<span style="color: #a5c261;">'a.appendChild(linkText);' </span>\
<span style="color: #a5c261;">'a.title = "%s";' </span>\
<span style="color: #a5c261;">'a.href = "%s";' </span>\
<span style="color: #a5c261;">'document.body.appendChild(a);' </span>% \
(url_to_use<span style="color: #cc7832;">, </span>url_to_use<span style="color: #cc7832;">, </span>url_to_use)
webdriver.execute_script(my_script)
webdriver.set_page_load_timeout(<span style="color: #6897bb;">20</span>)
webdriver.click_element_by_text(<span style="color: #a5c261;">'css=a'</span><span style="color: #cc7832;">, </span>url_to_use)
<span style="color: #cc7832; font-weight: bold;">if </span>page.loaded() <span style="color: #cc7832; font-weight: bold;">is </span><span style="color: #8888c6;">False</span>:
webdriver.click_element_by_text(<span style="color: #a5c261;">'css=a'</span><span style="color: #cc7832;">, </span>url_to_use)
<span style="color: #cc7832; font-weight: bold;">if </span>page.loaded() <span style="color: #cc7832; font-weight: bold;">is </span><span style="color: #8888c6;">False</span>:
webdriver.click_element_by_text(<span style="color: #a5c261;">'css=a'</span><span style="color: #cc7832;">, </span>url_to_use)
<span style="color: #cc7832; font-weight: bold;">if </span>page.loaded() <span style="color: #cc7832; font-weight: bold;">is </span><span style="color: #8888c6;">False</span>:
webdriver.click_element_by_text(<span style="color: #a5c261;">'css=a'</span><span style="color: #cc7832;">, </span>url_to_use)</pre>
<br />
In the above, page is my page object. The method loaded() checks controls to see if the page has finished loading. And click_element_by_text fetches matching elements and iterates through them to determine whether they have the text specified. If an element does, it clicks it. (Sorry I can't include that code, but it belongs to work, and would make this sample way too long.)<br />
<br />
In my experiments, it came to look like the integration between the driver and the browser (at least on Chrome) creates this state where Chrome has failed to load, but never tells the driver about it. So the driver just eventually times out.<br />
<br />
about://blank - I used this because it should render an internally generated page every time. On Chrome, it's a "This site can't be reached" error, which works just fine. Firefox and IE also generate errors or blank pages. But the assumption was that internally stored pages should load every time. And so far, they do.<br />
<br />
So by adding the target link and clicking it, I'm bypassing that tight integration.<br />
<br />
I've watched the code run, and I've seen it had to retry once in a while, but so far, never more than once.<br />
<br />
Please note this has only been tested on Chrome.<br />
<br />
I hope somebody finds this helpful! :)Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com6tag:blogger.com,1999:blog-425236375041542255.post-18072192431399449612016-05-31T15:19:00.000-07:002016-10-12T17:22:39.892-07:00The Nature of FrameworksIn my experience, when folks talk about test automation frameworks today, they're talking only about the libraries that you use to isolate things like selectors from the tests. And that is surely an important topic, one I'll address at some future time.<br />
<br />
But the 'automation framework' means a lot more than that. Back in the day, we had to think about:<br />
<br />
<ul>
<li>Where the source was</li>
<li>How we got it built, including all variants</li>
<li>Branching including which tests against which branches</li>
<li>Strategic prioritization of test implementation (eg, which tests are most important to implement first; what combinatorics; what mix of api, ui, performance and stress testing; etc)</li>
<li>Automated test management</li>
<li>Automated test data management (eg, preset database images to test against, data for data set tests)</li>
<li>Coding standards</li>
<li>Training of new staff</li>
<li>Design reviews</li>
<li>Code reviews</li>
<li>Portability, internationalization and localization</li>
<li>Automated test execution</li>
<li>Automated bug reporting</li>
<li>Artifact aging and control</li>
<li>Hardware resource allocation and maintenance</li>
<li>Crisis management</li>
<li>And QA Evangelizing</li>
</ul>
<br />
<br />
<div>
These days, it's a lot simpler, but it's still more than just libraries. In my current gig:</div>
<br />
<ul>
<li>Where the source was -- Github</li>
<li>How we got it built, including all variants -- Jenkins</li>
<li>Branching including which tests against which branches -- <b>Still managing this by hand</b></li>
<li>Strategic prioritization of test implementation -- <b>Still managing this by hand</b></li>
<li>Automated test management -- Nose and Python's unittest library</li>
<li>Automated test data management -- fixtures help, but mostly <b>Still managing this by hand</b></li>
<li>Coding standards -- PEP8 + <b>Our own standards</b></li>
<li>Training for new staff -- <b>Still managing this by hand</b></li>
<li>Design reviews -- <b>Still managing this by hand</b></li>
<li>Code reviews -- Github</li>
<li>Portability, internationalization and localization -- Honestly, I haven't run into that in my current gig</li>
<li>Automated test execution -- Nose and Jenkins</li>
<li>Automated bug reporting -- Jenkins</li>
<li>Artifact aging and control -- <b>Still managing this by hand</b></li>
<li>Hardware resource allocation and maintenance -- SauceLabs and various cloud services like Amazon or Google.</li>
<li>Crisis management -- <b>Still managing this by hand</b></li>
<li>And QA Evangelizing -- I have to admit, at my current gig, this hasn't been an issue.</li>
</ul>
<div>
While there are now systems to handle these things, they still need to be orchestrated. And as a Test Automation Architect, seeing to those systems and how they serve the delivery of useful results is still in my purview. But it sure is nice not to have to build all of it by hand. </div>
<div>
<br /></div>
<div>
This note has been percolating in my head for some time. It's in response to this: http://www.softwaretestinghelp.com/test-automation-frameworks-selenium-tutorial-20/</div>
<br />
<div>
<br /></div>
Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-13257073546722423902016-05-25T14:01:00.002-07:002016-05-25T14:01:25.561-07:00Rules and WhysWhen I started, I had to invent everything. It was 1990, and I didn't even have Google to ask.<br />
<br />
We discovered early on that some things needed to be *rules*. Like only page objects can talk to their controls. A hard and fast rule. And that one, with good reason. By enforcing encapsulation, maintenance is made far easier.<br />
<br />
When I started in my current gig, I was the most junior guy on the team, and the most junior with Selenium and Git... But the most experienced automation engineer overall by a factor of 5.<br />
<br />
Shortly after I started here, I had a code review with Clay Gould, our automation lead. In this code review, he got quite concerned about my use of a delay of a tenth of a second in the code. The rule he quoted amounted to 'never EVER use delays to synchronize, ALWAYS look for changes in the application or environment'. A good rule. One I've pounded into many new automators.<br />
<br />
When I teach and use these kinds of rules, I find it important to keep in mind why a rule exists. With time delays in code, the problem is use of the delay *for synchronization*. Because systems respond at different speeds at different times. Synchronization means attempting to line up processes. Like waiting for a page to be done loading before trying to take actions on it.<br />
<br />
I was not using the delay <b>for</b> synchronization, I was using it to <b>assist</b> in synchronization. I checked the state of something, paused for a 1/10th of a second, and checked the state again. I kept at this until a timeout was met.<br />
<br />
We argued about this for some time, and finally went looking at the Selenium code directly. Lo and behold, their code has a 3/10ths of a second delay in it in exactly the same fashion. It's to give over processor cycles to the other processes so they can complete more quickly... Rather than tying up cpu to test the condition over and over until it's met.<br />
<br />
More recently, I was implementing a selenium wrapper, and I wanted to spread out a delay across multiple functions. I wanted to validate that the control was present, visible, and enabled... And I wanted only a 20 second timeout overall. Which means exist can take a long time, and complete the other two quickly. Selenium has no native support for anything like this. I either specify different hard timeouts for each one, or I allow them to all add together, and come out with more than 20 seconds.<br />
<br />
The answer was to note my target end time (now + timeout) and pass the time remaining to each of the functions. And with a 1/10th of a second in between again.<br />
<br />
Rules are important. Rules are present for a reason. That reason may not be applicable in all circumstances. Experience often gives us the whys behind the rules, so we can assess when it's safe to ignore them. And in my experience, there are very few rules in test automation that don't have exceptions.<br />
<br />Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-37702925513483416042016-05-20T13:08:00.001-07:002016-05-20T13:08:27.786-07:00Getting Abstract...After I had my first glimmerings of OOP, my next learning was an extrapolation of having code in the window classes.<br />
<br />
I didn't know about abstractions or encapsulations yet, but I knew that having all these functions was producing too many things to take care of.<br />
<br />
I also knew that, if I put code into the window definitions, then the tests wouldn't have to know how to do a thing, only which window to ask to do it. It wasn't until quite some time later that I was told that this was both abstraction and encapsulation. I had learned to decouple the tests from the interface being tested. Yay!<br />
<br />
In my current position, our tests make use of page objects to accomplish this same thing. And it's a decent start. One of our rules is tests never touch controls directly, they call functions in the page object. A good idea.<br />
<br />
One of the things I observe in the code I've inherited joining my current gig was that often, instead of clicking a link, the tests here would call a function like page.click_link_to_next_page()<br />
<br />
I'm looking to change this, because this means the test is still coupled to the interface, but now we wrap the selector in a function. Which actually doesn't accomplish the goal.<br />
<br />
Coming next.. about rules and whys.<br />
<br />
<br />Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-65686273013843538132016-05-19T11:57:00.001-07:002016-05-20T13:09:18.841-07:00OOPs!In 1990, I was working at Symantec on desktop project planning software. I'd figured out that I needed low level access to controls to implement good automation. So I began to try and write it.<br />
<br />
I had just started to get the very first bits of prototype code to work. Yay! And then my boss, Jennifer Flaa, came back from a QA conference. She told me that while she was there, she'd seen a tool called QA Workbench, built by a company in Boston called Segue. And she wanted to fly me out to look at it.<br />
<br />
I went, and met with Lawrence Kepple and David LaRoche. They gave me the demo. It was everything I was trying to build! Woot! It programmed in a language called 4Test, based on C. It gave me low level access to everything. I got to be the first external beta tester for them. Yay!<br />
<br />
In order to build a test suite, I had to produce these huge long tables of constants, rather like the selectors in Selenium. Once those were built, tests were made by producing scripts that passed those constants to functions, and suddenly my dream of actually being able to access all the controls was a reality.<br />
<br />
And these lists of constants gave me my next lesson in test automation. They were pretty difficult to maintain. And I wasn't the only one having trouble.<br />
<br />
Segue got enough feedback that when they introduced QA Workbench 1.0, it included a limited form of object oriented programming. We could create objects to represent windows, and within them, objects to represent controls. These classes were created using a directive called 'winclass', and we were told they were for creating a GUI representation.<br />
<br />
And each of the classes had methods and properties<br />
<br />
It was a stretch, I was learning the basics of objects on the job in real time. But it was an awesome way to organize these vast tables.<br />
<br />
We had a control who's selector was different at different times. David explained to me that I could add code into the object to return the appropriate selector. And that's where my love of oop started.<br />
<br />
QA Workbench went on to become SilkTest. Which I used for many, many years after that.<br />
<br />
Coming next... AbstractionsAkienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-7568465337891165862016-05-17T13:59:00.001-07:002016-05-19T10:32:32.627-07:00Automate All The Things! Not...When I started on test automation, I had the intention to automate all of the testing.<br />
<br />
It was really a ludicrous idea, because at the time, we had enough trouble just keeping up with the UI changes.<br />
<br />
What I learned was that automation was better for:<br />
<ul>
<li>Smoke testing before deploying a build to QA</li>
<li>Regression testing of existing features</li>
<li>Performing extremely repetitive testing</li>
<li>Running performance and stress tests</li>
</ul>
<div>
I've been asked my a couple of companies to automate all the tests, and I surely did try. When I and my team couldn't do it, I tried creating libraries that made automation easy for everybody to implement. I even wrote a keyword based system at one point (thanks, Rafael Santander!).</div>
<div>
<br /></div>
<div>
In addition to the limitations above, I've learned that strategic implementation is critical. OK, so you want me to automate all the regression tests, let's start with the tests that will meet a specific business need. Whether that's smoke tests (and it often is) or tests for strategically important pieces of application under test. </div>
<div>
<br /></div>
<div>
Since those days, a little analysis before hand about the requirements on automation has served me well. </div>
Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0tag:blogger.com,1999:blog-425236375041542255.post-77063446396209814512016-05-16T16:25:00.001-07:002016-05-19T10:28:41.942-07:00Crawling around in the gutsI have learned about how important low level access is to automated testing.<br />
<br />
We take it for granted today... Tools like Selenium, SilkTest, UFT, and a bazillion other tools, all provide this access.<br />
<br />
When I first started doing automated testing, there wasn't a roadmap. It was 1990, David Blair, my boss, came to me and told me to do test automation. I asked him what it was, and he said "I don't know, go figure it out". And thus was my career born.<br />
<br />
I started by using a tool that would allow me to see which window was active (by window title), I could ask if a given window existed, and I could send keystrokes to an app. Since they relied on keystrokes, I had to count tabs, and use alt-key commands and so on in order to build my tests.<br />
<br />
About all I could do was try things and see if error windows came up. Those were my first smoke tests.<br />
<br />
And they were a nightmare to keep going.<br />
<br />
After the very first project was done, it was clear to me that this was very limited and very time consuming.<br />
<br />
I needed something better. I needed a tool with lower level access. I needed to be able to talk to the control objects in the OS directly.<br />
<br />
In my experience, this isn't something we think about anymore. Tools to do this have now existed for a long time, so this snippet is more historical than current. Nonetheless, it was my first lesson in automation.<br />
<br />Akienhttp://www.blogger.com/profile/04841505056487737661noreply@blogger.com0