<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>performance - Deployment Every Day</title>
	<atom:link href="http://deploymenteveryday.com/category/performance/feed/" rel="self" type="application/rss+xml" />
	<link>http://deploymenteveryday.com</link>
	<description>JVM (java/kotlin) performance, programming, frequent deployments, devops</description>
	<lastBuildDate>Mon, 23 Dec 2024 11:24:18 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
	<item>
		<title>Is (website) performance improved after improving percentile 90?</title>
		<link>http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=is-website-performance-improved-after-improving-percentile-90</link>
					<comments>http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 09 Sep 2024 15:14:25 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[distribution]]></category>
		<category><![CDATA[fcp]]></category>
		<category><![CDATA[first contentful paint]]></category>
		<category><![CDATA[p90]]></category>
		<category><![CDATA[percentile]]></category>
		<category><![CDATA[statistics]]></category>
		<category><![CDATA[web vitals]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=236</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<p class="responsive-video-wrap clr"><iframe title="Is (website) performance improved when percentile 90 is improved?" width="1200" height="675" src="https://www.youtube.com/embed/1Fbi2EgqUpM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
</div></figure><p>The post <a href="http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/">Is (website) performance improved after improving percentile 90?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/is-website-performance-improved-after-improving-percentile-90/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>No More Guesswork: Validating what improves First Contentful Paint upfront with No-Code Experiments</title>
		<link>http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments</link>
					<comments>http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 12 Aug 2024 15:11:41 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[fcp]]></category>
		<category><![CDATA[first contentful paint]]></category>
		<category><![CDATA[web vitals]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=205</guid>

					<description><![CDATA[<p>Let&#8217;s take a look at performance of 2 websites with a goal to improve it. Let&#8217;s use FCP (First Contentful Paint) as a performance metric, which is a core google web vital metric. Following tools are used to verify website performance and get fix suggestions I&#8217;m going to use those tools to see Let&#8217;s start [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/">No More Guesswork: Validating what improves First Contentful Paint upfront with No-Code Experiments</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">Let&#8217;s take a look at performance of 2 websites with a goal to improve it. Let&#8217;s use <code>FCP</code> (<code>First Contentful Paint</code>) as a performance metric, which is a core google web vital metric.</h1>



<p class="wp-block-paragraph">Following tools are used to verify website performance and get fix suggestions</p>



<ul class="wp-block-list">
<li>https://pagespeed.web.dev/
<ul class="wp-block-list">
<li>tool that says what potential performance problems a website has</li>
</ul>
</li>



<li>and my own <code>performance-explainer</code> which aims to explain
<ul class="wp-block-list">
<li>what exactly needs to fixed</li>



<li>what the <code>ROI</code> (return of investment) of each fix is &#8211; to focus on fixes giving the biggest improvement</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">I&#8217;m going to use those tools to see</p>



<ul class="wp-block-list">
<li>What the user perceived performance is</li>



<li>How to fix performance</li>
</ul>



<p class="wp-block-paragraph">Let&#8217;s start with website 1</p>



<h2 class="wp-block-heading"><code>PageSpeed insights</code> website 1</h2>



<p class="wp-block-paragraph">Let&#8217;s see what a popular tool says about performance of website https://donorperfect.com. That&#8217;s just a sample website that was sent to me during early phase of developing <code>performance explainer</code> to check what is says about performance of this website.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="744" height="1024" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-744x1024.png" alt="donorperfect.com - PageSpeed insights scoring" class="wp-image-209" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-744x1024.png 744w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-218x300.png 218w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1-768x1057.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-pagespeed-1.png 896w" sizes="(max-width: 744px) 100vw, 744px" /></figure>



<p class="wp-block-paragraph">It says overall performance is poor. <code>FCP</code>, which is our focus in this blog post, is scored medium.</p>



<p class="wp-block-paragraph">Let&#8217;s take a look at <code>FCP</code> improvement suggestions from <code>PageSpeed insights</code>.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="975" height="966" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all.png" alt="" class="wp-image-210" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all.png 975w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all-300x297.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all-150x150.png 150w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-all-768x761.png 768w" sizes="(max-width: 975px) 100vw, 975px" /></figure>



<h3 class="wp-block-heading">Problem 1: There are render blocking resources slowing down FCP</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="971" height="524" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1.png" alt="" class="wp-image-212" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1.png 971w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1-300x162.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-1-768x414.png 768w" sizes="(max-width: 971px) 100vw, 971px" /></figure>



<p class="wp-block-paragraph">It says potential saving is 1,050 ms. That would be a huge <code>FCP</code> boost and much better use experience when fixes applied!</p>



<p class="wp-block-paragraph"><strong>So as a developer you might think: if I or my team puts an effort to fix blocking resources, performance will be fixed. Right?</strong> More on that later <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h3 class="wp-block-heading">Problem 2: big images</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="877" height="742" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix.png" alt="" class="wp-image-213" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix.png 877w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix-300x254.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-pagespeed-insights-fcp-suggested-images-fix-768x650.png 768w" sizes="(max-width: 877px) 100vw, 877px" /></figure>



<p class="wp-block-paragraph"><strong>Ok. Looks easy. If images are compressed more, those should be sent faster and should speed up FCP. Right?</strong></p>



<p class="wp-block-paragraph">At this point a developer trying to improve a website might think:</p>



<ul class="wp-block-list">
<li>It&#8217;s clear how to fix performance</li>



<li>And a there might be a shy thought <em>When I apply the fix, how do I know it really improved <code>FCP</code>?</em>
<ul class="wp-block-list">
<li><code>PageSpeed insights</code> does not aswer this question before the fix.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Is the performance tool job done? Well, if the goal is to actually improve performance, that&#8217;s merely a beginning of the job. After the fix developer needs to know if performance was really fixed. That requires measuring the system again. The tool needs to be a part of development cycle.</p>



<h3 class="wp-block-heading">Hold on. Let&#8217;s use much more time effective approach. <code>performance explainer</code> in action</h3>



<p class="wp-block-paragraph">First: take many samples and see what the performance really is.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="445" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-1024x445.png" alt="" class="wp-image-214" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-1024x445.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-300x130.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark-768x334.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-benchmark.png 1311w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Let&#8217;s apply acceptance criteria: For 99% of visits, FCP should be below 1s.</p>



<p class="wp-block-paragraph">Is it achieved?</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="536" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-1024x536.png" alt="" class="wp-image-215" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-1024x536.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-300x157.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria-768x402.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donofperfect-fcp-acceptance-criteria.png 1306w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Unfortunately not. Within 150 samples, FCP below 1s was never experienced.</p>



<p class="wp-block-paragraph">Let&#8217;s take a look at FCP across all samples on a percentile chart. What&#8217;s the latency user experiences?</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="662" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-1024x662.png" alt="" class="wp-image-216" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-1024x662.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-300x194.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles-768x496.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-fcp-distribution-percentiles.png 1306w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Now you can see the big picture what&#8217;s going on at this website. 2.3s as suggested by <code>PageSpeed insights</code> never happened. Maybe <code>PageSpeed insights</code> analyzed samples when that indeed happened?</p>



<h3 class="wp-block-heading">Let&#8217;s apply fixes suggested by <code>PageSpeed insights</code> with a no-code experiments and see if <code>FCP</code> is fixed.</h3>



<p class="wp-block-paragraph"><code>performance-explainer</code> can make a no code experiments in the real browser to answer question: &#8220;<em>how much boosting particular requests impacts the measurement?</em>&#8220;. In this case &#8211; How much will it improve <code>FCP</code>?</p>



<p class="wp-block-paragraph">So let&#8217;s pick requests too boost.</p>



<h4 class="wp-block-heading">Boost render blocking resources suggested by <code>PageSpeed</code> to simulate the fix without any coding</h4>



<p class="wp-block-paragraph">What if I speed up those requests to a possible maximum? For example, if original request in the browser takes 400ms, it&#8217;s possible to cut down the latency in experiment to ~50ms. This way you can see what would happen if you changed the production system.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1302" height="781" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-multi-boost-render-blocking-resources.png" alt="" class="wp-image-217" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-multi-boost-render-blocking-resources.png 1302w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-multi-boost-render-blocking-resources-300x180.png 300w" sizes="(max-width: 1302px) 100vw, 1302px" /></figure>



<p class="wp-block-paragraph">Ok, go!</p>



<p class="wp-block-paragraph">A few minutes later the result is ready. 400 baseline measurements (original website) compared to 400 experiment measurements (selected resourced served to browser almost instantly).<strong><br> <br> What&#8217;s the result? Does implementing <code>PageSpeed insights</code> suggestions would fix FCP? No. Developers would work on something that doesn&#8217;t improve it<br></strong></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="311" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-1024x311.png" alt="" class="wp-image-218" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-1024x311.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-300x91.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix-768x233.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pageinsight-suggested-resources-fix.png 1322w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">If you speed up those requests by 75-88% those will have no impact on <code>FCP</code>. In production it would mean either actually sending them faster or compressing. Neither would help fixing <code>FCP</code>.</p>



<p class="wp-block-paragraph">Depending on the product, company and the developers team &#8211; fixing performance might take anything from a few hours to a few weeks. So in case of a false signal, a lot of time and money could be wasted.</p>



<h4 class="wp-block-heading">Another <code>PageSpeed insights</code> hint is to fix images size. Let&#8217;s make a no-code experiment and boost sending images</h4>



<p class="wp-block-paragraph">Wha&#8217;t the result of boosting images that are supposed to improve FCP?</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1309" height="535" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pagespeed-insight-suggested-images-fix.png" alt="" class="wp-image-219" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pagespeed-insight-suggested-images-fix.png 1309w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-apply-pagespeed-insight-suggested-images-fix-300x123.png 300w" sizes="(max-width: 1309px) 100vw, 1309px" /></figure>



<p class="wp-block-paragraph">Note images are boosted almost 100%. That means those are almost instantly available for the browser yet it would have no impact on <code>FCP</code>.</p>



<p class="wp-block-paragraph"><strong>Devs would again work on something not improving FCP!</strong></p>



<h4 class="wp-block-heading">So what will actually improve FCP?</h4>



<p class="wp-block-paragraph">This particular website makes ~120 requests. Which one impacts <code>FCP</code>?</p>



<p class="wp-block-paragraph">It&#8217;s possible to tell with a static analysis. But which one has impact strong enough to make it <strong>meaningful and worth fixing</strong>?</p>



<p class="wp-block-paragraph">It&#8217;s impossible to tell without statistical analysis based on taking samples from real system. Which means using browser, just like user does.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="697" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-1024x697.png" alt="" class="wp-image-220" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-1024x697.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-300x204.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90-768x523.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfect-browser-waterfall-p90.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Some of requests are not even ever started before <code>FCP</code> across all samples &#8211; that means there are requests very likely to be irrelevant for measurement. But you can&#8217;t tell that doing 1 or a few checks. If you take hundreds of samples, you can start making conclusions how tested system really behaves.</p>



<p class="wp-block-paragraph">&#8220;<em><code>PageSpeed</code> shows suggestions that do not help fixing <code>FCP</code>. What to do now?</em> &#8220;</p>



<p class="wp-block-paragraph"><strong>Request boos exploration for the rescue!</strong></p>



<p class="wp-block-paragraph">I&#8217;ve worked really hard to implement request boost exploration in <code>performance explainer</code>.</p>



<p class="wp-block-paragraph">The methodology is the following:</p>



<ul class="wp-block-list">
<li>For every single request browser makes at given website</li>



<li>In the real browser simulate request boost up from 10 to 100% (incrementally)</li>



<li>Take hundreds of samples and apply statistics to see what&#8217;s the noise and what&#8217;s the true impact
<ul class="wp-block-list">
<li>It&#8217;s not using production system, everything happens in the browsers with requests fulfilled with controlled latencies</li>
</ul>
</li>



<li>Tell if it&#8217;s worth speeding up or not</li>
</ul>



<p class="wp-block-paragraph">Instead of guessing what to do, let the tooling figure out what&#8217;s worth fixing. It might take some time, but the results are accurate.</p>



<p class="wp-block-paragraph">The lower the measurement latency impact, the better.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="752" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-1024x752.png" alt="" class="wp-image-222" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-1024x752.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-300x220.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix-768x564.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/donorperfekt-actual-fcp-fix.png 1317w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Are those easy fixes? Myabe it&#8217;s just a matter of deferring the js resource or making it async. I don&#8217;t know. Latency fixes are often difficult. That&#8217;s something that now developers can figure out.</p>



<p class="wp-block-paragraph">Huge part of the job is already done &#8211; knowing what to improve. The more first request from above table is sped up, the better <code>FCP</code> is. And that&#8217;s where the biggest <code>ROI</code> for improving <code>FCP</code> is. You could also speed up main site, but the <code>ROI</code> there is smaller.</p>



<h3 class="wp-block-heading">Main focus of this blog post is FCP, but let&#8217;s take at LCP (Largest Contentful Paint) also. How to improve it?</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="675" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-1024x675.png" alt="" class="wp-image-221" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-1024x675.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-300x198.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix-768x506.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/blog-post-pagespeed-insights-fcp-actual-lcp-fix.png 1333w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><code>LCP</code> has a different bottleneck. It&#8217;s an image, with maximum possible improvement opportunity 65%. What improves LCP might not improve FCP (as in this case). Because it &#8216;s a different goal.</p>



<p class="wp-block-paragraph">Note that there is better <code>LCP</code> when image is sped up 80% than 99%. Why? It sounds counterintuitive.</p>



<p class="wp-block-paragraph">Because there are request races in the browser.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">ANSI</span></span><span role="button" tabindex="0" data-code="// R1, R2, R3 - browser requests
// R1 is most likely to be a LCP bottleneck

R1 --------------------------------------------
R2 ------------------------------
R3 -------------------------------------

// after enough R1 boost LCP has no further improvements
// now R3 is most likely to be a LCP bottleneck
R1 ----------------------
R2 ------------------------------
R3 -------------------------------------" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #d8dee9ff">// R1, R2, R3 - browser requests</span></span>
<span class="line"><span style="color: #d8dee9ff">// R1 is most likely to be a LCP bottleneck</span></span>
<span class="line"></span>
<span class="line"><span style="color: #d8dee9ff">R1 --------------------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R2 ------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R3 -------------------------------------</span></span>
<span class="line"></span>
<span class="line"><span style="color: #d8dee9ff">// after enough R1 boost LCP has no further improvements</span></span>
<span class="line"><span style="color: #d8dee9ff">// now R3 is most likely to be a LCP bottleneck</span></span>
<span class="line"><span style="color: #d8dee9ff">R1 ----------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R2 ------------------------------</span></span>
<span class="line"><span style="color: #d8dee9ff">R3 -------------------------------------</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">ANSI</span></div>



<p class="wp-block-paragraph">Systems don&#8217;t behave deterministic, that&#8217;s why hundreds of samples need to be taken. There is a lot of going on in the browser. If you cut off latency of a single request enough, you&#8217;re going to see the improvement and after that new bottleneck might be uncovered.</p>



<p class="wp-block-paragraph">That&#8217;s a lo of knowledge, I&#8217;m glad you&#8217;re still here <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">Now let&#8217;s move on to</p>



<h2 class="wp-block-heading">PageSpeed insights: website 2</h2>



<p class="wp-block-paragraph">I feel at the moment of writing my wordpress blog you&#8217;re just at is not fast. So let&#8217;s just measure it.</p>



<h3 class="wp-block-heading">What does PageSpeed insight say?</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="745" height="1024" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-745x1024.png" alt="" class="wp-image-223" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-745x1024.png 745w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-218x300.png 218w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights-768x1055.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-pagespeed-insights.png 882w" sizes="(max-width: 745px) 100vw, 745px" /></figure>



<p class="wp-block-paragraph">It says 1.8s for <code>FCP</code>, 6.2s for <code>LCP</code>. It could be better.</p>



<p class="wp-block-paragraph">However does it mean every user experiences FCP 1.8s? Or maybe that&#8217;s the worst case? Or the best?</p>



<p class="wp-block-paragraph">How about fixes?</p>



<p class="wp-block-paragraph">It points initial server response time could be better as it&#8217;s 1 140 ms</p>



<ul class="wp-block-list">
<li>Is it where most improvement can be made? I don&#8217;t know but it&#8217;s the biggest latency shown on the list.</li>



<li>Other fixes show potential [kb] savings.
<ul class="wp-block-list">
<li>What does it mean in terms of latency? How much will it improve FCP? Is it worth investing my time to apply fixes there?</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">I value my time. I need data telling me what should be improved and in what order, not only what&#8217;s wrong without even knowing if it will help.</p>



<h3 class="wp-block-heading">What does <code>performance explainer</code> <code>FCP</code> benchmark say?</h3>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="605" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-1024x605.png" alt="" class="wp-image-224" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-1024x605.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-300x177.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark-768x454.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-benchmark.png 1311w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">It says user never experiences the desired FCP below or equal 1s.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="638" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-1024x638.png" alt="" class="wp-image-225" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-1024x638.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-300x187.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck-768x479.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-phases-bottleneck.png 1311w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">It also says <code>TTFB</code> (<code>Time To First Byte</code>) is where 72% of time user is waiting for <code>FCP</code> and that it needs to be sped up 54.55% to meet acceptance criteria and have 99% latencies below 1s.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="662" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-1024x662.png" alt="" class="wp-image-226" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-1024x662.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-300x194.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles-768x497.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-distribution-percentiles.png 1310w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><code>FCP</code> takes from 1326 ms to 4807 ms across 150 measurements. Ouch. I want to give my readers a better experience.</p>



<p class="wp-block-paragraph">FCP at 1.8s level shown in <code>PageSpeed</code> didn&#8217;t look that bad.</p>



<p class="wp-block-paragraph">So let&#8217;s figure out ROIs using <code>request boost exploration</code> in <code>performance explainer</code></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="518" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-1024x518.png" alt="" class="wp-image-227" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-1024x518.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-300x152.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix-768x389.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-actual-fcp-fix.png 1316w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The only fix worth investing is fixing <code>TTFB</code>. Not resources mentioned by <code>PageSpeed insights</code>.</p>



<p class="wp-block-paragraph">I&#8217;m no wordpress expert, but I thought &#8220;<em>How about caching the whole response to spend less time on producing HTML? There must be some plugins for caching</em>&#8220;</p>



<p class="wp-block-paragraph">So I installed <code>WP Fastest Cache</code> plugin, turned it on with default settings hoping to improve <code>TTFB</code> which is a bottleneck for <code>FCP</code>. Immediately I had questions in my mind.</p>



<ul class="wp-block-list">
<li>Did it improve performance?</li>



<li>How would I know it?</li>



<li>Would refreshing page in the browser give the <em>feeling</em> it&#8217;s better?</li>



<li>What if it gave the <em>feeling</em> it&#8217;s better, but if I refreshed the page 10 more times it would actually give <em>feeling</em> it&#8217;s worse?</li>
</ul>



<p class="wp-block-paragraph">Let the data speak. And what does it say after taking samples and comparing it to samples before installing cache plugin?</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="231" src="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-1024x231.png" alt="" class="wp-image-228" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-1024x231.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-300x68.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment-768x173.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/08/deployment-every-day-fcp-cache-plugin-experiment.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><code>FCP</code> did not improve at all. Without measuring it after installing plugin and comparing to baseline (no plugin installed) I&#8217;d be pretty sure performance is better. While my readers would feel no difference at all.</p>



<p class="wp-block-paragraph">Additional plugin would only add complexity to the system. And I would think it&#8217;s improving the situation. I removed the plugin immediately.</p>



<p class="wp-block-paragraph">Now I have more ideas for speeding up <code>TTFB</code>.</p>



<ul class="wp-block-list">
<li>better hosting</li>



<li>different caching plugin</li>



<li>different skin</li>



<li>&#8230; and there might be 10 more ideas</li>
</ul>



<p class="wp-block-paragraph">However that&#8217;s a typical <code>trial and error</code> fixing methodology when no data is available for showing bottleneck in more details.</p>



<p class="wp-block-paragraph"><strong>It&#8217;s easy to produce ideas. It&#8217;s expensive to implement them. And it&#8217;s risky because it might fix nothing.</strong></p>



<p class="wp-block-paragraph">When you think a fix helped (while it didn&#8217;t), unnecessary complexity grows over time. It contributes to a growing production systems maintenance cost.</p>



<p class="wp-block-paragraph">Since I know the bottleneck is PHP backend, what would really help is to have profiler samples. Or maybe some plugin showing what&#8217;s slow on wordpress backend if there is such one.</p>



<p class="wp-block-paragraph">It would show where time is spent when request is made from the browser. In another words &#8211; data allowing understand better the root cause would appear.</p>



<p class="wp-block-paragraph">If I knew PHP backend bottleneck, there would be no guessing what to fix. As a result</p>



<ul class="wp-block-list">
<li>much quicker fix delivery</li>



<li>less time spent</li>



<li>no unjustifiable complexity added for solutions that were supposed to fix performance, while fixing nothing</li>
</ul>



<h2 class="wp-block-heading"><code>PageSpeed isights</code> VS <code>performance explainer</code> summary</h2>



<p class="wp-block-paragraph"><code>PageSpeed insights</code></p>



<ul class="wp-block-list">
<li>It shows improvement ideas. Those look good and worth spending time on
<ul class="wp-block-list">
<li>But in reality those fixes might not fix performance at all</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><code>performance explainer</code></p>



<ul class="wp-block-list">
<li>It&#8217;s showing the whole picture and how often user experiences good and bad performance.
<ul class="wp-block-list">
<li>Thanks to to hundreds of samples taken</li>
</ul>
</li>



<li>it does not present good sounding fixes ideas. It presents the ones that actually impact performance</li>
</ul>



<h3 class="wp-block-heading">Do you want to know if your website (on premise product too) is fast enough and having it fixed if needed?</h3>



<p class="wp-block-paragraph">Reach me at mikolaj.grzaslewicz@gmail.com if you want to have performance of your website fixed via consultations. My goal is to have <code>performance explainer</code> available as a service to support more customers.</p><p>The post <a href="http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/">No More Guesswork: Validating what improves First Contentful Paint upfront with No-Code Experiments</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/no-more-guesswork-validating-what-improves-first-contentful-paint-upfront-with-no-code-experiments/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Want to have better accuracy of performance benchmark? Stop using playwright Page.waitForCondition</title>
		<link>http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition</link>
					<comments>http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Mon, 13 May 2024 09:21:53 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[plawright]]></category>
		<category><![CDATA[polling]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=168</guid>

					<description><![CDATA[<p>Let&#8217;s compare accuracy of Page.waitForCondition with custom condition polling For measurements I&#8217;m going to use 2 different approaches to see which one is better for performance benchmarks and has better accuracy. Custom condition polling implementation Website 1: https://www.theathletesfoot.nl/ Measurement ends when button starts working. Starts working means, it&#8217;s reacting on clicks. At this particular website [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/">Want to have better accuracy of performance benchmark? Stop using playwright Page.waitForCondition</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">Let&#8217;s compare accuracy of <code>Page.waitForCondition</code> with custom condition polling</h1>



<p class="wp-block-paragraph">For measurements I&#8217;m going to use 2 different approaches to see which one is better for performance benchmarks and has better accuracy.</p>



<ul class="wp-block-list">
<li>first using playwright <code>Page.waitForCondition</code></li>



<li>and the second using custom <code>condition polling</code></li>
</ul>



<p class="wp-block-paragraph">Custom <code>condition polling</code> implementation</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="// part of performance-explainer project I'm developing to measure things

package com.performance.explainer.api.waiter

import com.performance.explainer.spi.waiter.ConditionPolling
import java.time.Duration
import java.time.Instant
import java.util.function.BooleanSupplier


class ConditionPolling : SpiConditionPolling {

    override fun await(
        retryWait: Duration,
        timeout: Duration,
        condition: BooleanSupplier
    ): Boolean {
        val start = Instant.now()
        while (true) {
            val satisfied = condition.asBoolean
            val elapsed = Duration.between(start, Instant.now())
            if (satisfied) {
                return true
            }
            if (elapsed.plus(retryWait) &gt; timeout) {
                return false
            }
            Thread.sleep(retryWait)
        }
    }

}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// part of performance-explainer project I&#39;m developing to measure things</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">package</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.spi.waiter.ConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Instant</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.BooleanSupplier</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ConditionPolling</span><span style="color: #D8DEE9FF"> : SpiConditionPolling {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">override</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fun</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        retryWait: Duration,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        timeout: Duration,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        condition: BooleanSupplier</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ): Boolean {</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> start </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> Instant.</span><span style="color: #88C0D0">now</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">while</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">true</span><span style="color: #D8DEE9FF">) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> satisfied </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> condition.asBoolean</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> elapsed </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> Duration.</span><span style="color: #88C0D0">between</span><span style="color: #D8DEE9FF">(start, Instant.</span><span style="color: #88C0D0">now</span><span style="color: #D8DEE9FF">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (satisfied) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">            }</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (elapsed.</span><span style="color: #88C0D0">plus</span><span style="color: #D8DEE9FF">(retryWait) </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> timeout) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">            }</span></span>
<span class="line"><span style="color: #D8DEE9FF">            Thread.</span><span style="color: #88C0D0">sleep</span><span style="color: #D8DEE9FF">(retryWait)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        }</span></span>
<span class="line"><span style="color: #D8DEE9FF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">NonBlockingConditionPolling</span></div>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Website 1: https://www.theathletesfoot.nl/</h2>



<p class="wp-block-paragraph">Measurement ends when button starts working. Starts working means, it&#8217;s reacting on clicks. At this particular website it means validation error shows up.</p>



<p class="wp-block-paragraph">That unfortunately cannot be checked with locator and <code>.click()</code> as button is not disabled and it&#8217;s clickable before it has event listeners attached. That could be fixed with waiting for <code>loaded</code> event, but that&#8217;s a bad idea for performance benchmark as explained with details in this blog post. So below code is <strong>NOT working</strong> for checking if button is working. Take a look at the explanation in source code</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.TimeoutError
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
    page.navigate(
        &quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;,
        NavigateOptions().setWaitUntil(COMMIT)
    )
    val button = page.locator(&quot;#add-to-cart&quot;)
    button.waitFor(WaitForOptions().setTimeout(3000.0))
    // plawright waits for a clickable button.
    // Clickable means it's visible and not disabled
    // This button is visible and not disabled before all resources are loaded.
    // Because of that, it does not actually measure user experience (button is working) 
    button.click()
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.TimeoutError</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #A3BE8C">&quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    )</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#add-to-cart&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// plawright waits for a clickable button.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// Clickable means it&#39;s visible and not disabled</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// This button is visible and not disabled before all resources are loaded.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">// Because of that, it does not actually measure user experience (button is working) </span></span>
<span class="line"><span style="color: #D8DEE9FF">    button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">Broken measurement</span></div>



<figure class="wp-block-image size-large is-style-zoooom wp-duotone-unset-1"><img decoding="async" width="1024" height="529" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-1024x529.png" alt="" class="wp-image-175" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-1024x529.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-300x155.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-768x397.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example-1536x794.png 1536w, http://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example.png 1567w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example.webm"></video><figcaption class="wp-element-caption">Measurement target &#8211; wait for working button adding item to the shop cart</figcaption></figure>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">baseline 1 &#8211; wait for working button, use custom polling</h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.TimeoutError
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import com.performance.explainer.api.waiter.NonBlockingConditionPolling
import java.time.Duration.ofMillis
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   val conditionPolling = NonBlockingConditionPolling.default()
   page.navigate(
       &quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.locator(&quot;#add-to-cart&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   val success = conditionPolling.await(
       retryWait = ofMillis(10),
       timeout = ofMillis(5000)
   ) {
       button.click()
       val formError = page.locator(&quot;.js-select-size-and-color-error&quot;)
       formError.count() == 1
   }
   if (!success) {
       throw TimeoutError(&quot;Waiting for working button timed out&quot;)
   }
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.TimeoutError</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter.NonBlockingConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofMillis</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> conditionPolling </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> NonBlockingConditionPolling.</span><span style="color: #88C0D0">default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#add-to-cart&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> success </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> conditionPolling.</span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       retryWait </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">       timeout </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">5000</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   ) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> formError </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;.js-select-size-and-color-error&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">       formError.</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">success) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TimeoutError</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Waiting for working button timed out&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">baseline 1 &#8211; wait for working button, use custom polling</span></div>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">experiment 1 &#8211; wait for working button, use <code>Page.waitForCondition</code></h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(
       &quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.locator(&quot;#add-to-cart&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   page.waitForCondition({
       button.click()
       val formError = page.locator(&quot;.js-select-size-and-color-error&quot;)
       formError.count() == 1
   }, Page.WaitForConditionOptions().setTimeout(5000.0))
}
// measured latency is duration of above
" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.theathletesfoot.nl/producten/dames/air-max-90-sneakers/iic.nike.fz5163.133.html&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#add-to-cart&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">waitForCondition</span><span style="color: #D8DEE9FF">({</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> formError </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;.js-select-size-and-color-error&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">       formError.</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }, Page.</span><span style="color: #88C0D0">WaitForConditionOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">5000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span>
<span class="line"></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">experiment 1 &#8211; wait for working button, use Page.waitForCondition</span></div>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Website 2: https://www.blogmarketingacademy.com/</h2>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="562" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-1024x562.png" alt="" class="wp-image-177" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-1024x562.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-300x165.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example-768x422.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example.png 1457w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Measurement target &#8211; wait for working subscribe button</figcaption></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example.webm"></video><figcaption class="wp-element-caption">Measurement target &#8211; wait for working subscribe button</figcaption></figure>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">baseline 2 &#8211; wait for working button, use custom polling</h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.*
import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import com.performance.explainer.api.waiter.NonBlockingConditionPolling
import java.time.Duration.ofMillis
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(
       &quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.getByText(&quot;Subscribe Weekly&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   val condition = NonBlockingConditionPolling.default()
   val isButtonWorking = condition.await(retryWait = ofMillis(10), timeout = ofMillis(3000)) {
       button.click()
       page.getByText(&quot;This field is required&quot;).count() == 1
   }
   if (!isButtonWorking) {
       throw TimeoutError(&quot;Waiting for working button timed out&quot;)
   }
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.</span><span style="color: #81A1C1">*</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter.NonBlockingConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofMillis</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Subscribe Weekly&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> condition </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> NonBlockingConditionPolling.</span><span style="color: #88C0D0">default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> isButtonWorking </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> condition.</span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(retryWait </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF">), timeout </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000</span><span style="color: #D8DEE9FF">)) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;This field is required&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">isButtonWorking) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TimeoutError</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Waiting for working button timed out&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">baseline 2 &#8211; wait for working button, use custom polling</span></div>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">experiment 2 &#8211; wait for working button, use <code>Page.waitForCondition</code></h3>



<div class="wp-block-kevinbatdorf-code-block-pro padding-bottom-disabled cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(
       &quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;,
       NavigateOptions().setWaitUntil(COMMIT)
   )
   val button = page.getByText(&quot;Subscribe Weekly&quot;)
   button.waitFor(WaitForOptions().setTimeout(3000.0))
   page.waitForCondition({
       button.click()
       page.getByText(&quot;This field is required&quot;).count() == 1
   }, Page.WaitForConditionOptions().setTimeout(3000.0))
}
// measured latency is duration of above" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #A3BE8C">&quot;https://www.blogmarketingacademy.com/membership-site-service-credit-system/&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   )</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> button </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Subscribe Weekly&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   button.</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">waitForCondition</span><span style="color: #D8DEE9FF">({</span></span>
<span class="line"><span style="color: #D8DEE9FF">       button.</span><span style="color: #88C0D0">click</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">       page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;This field is required&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">count</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }, Page.</span><span style="color: #88C0D0">WaitForConditionOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">3000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"><span style="color: #616E88">// measured latency is duration of above</span></span></code></pre><span style="display:flex;align-items:flex-end;padding:10px;width:100%;justify-content:flex-end;background-color:#2e3440ff;color:#c8d0e0;font-size:12px;line-height:1;position:relative">experiment 2 &#8211; wait for working button, use Page.waitForCondition</span></div>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Custom polling VS <code>Page.waitForCondition</code> results</h1>



<p class="wp-block-paragraph">Each baseline and experiment has 200 samples taken. <a href="https://en.wikipedia.org/wiki/Hodges%E2%80%93Lehmann_estimator" target="_blank" rel="noopener" title="">Hodges-Lehmann</a> estimator is used for comparing results</p>



<h2 class="wp-block-heading">baseline 1 VS experiment 1</h2>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="874" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-1024x874.png" alt="" class="wp-image-180" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-1024x874.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-300x256.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-768x655.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1.png 1301w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code> 7.958% worse than custom polling</figcaption></figure>



<p class="wp-block-paragraph"></p>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="898" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-1024x898.png" alt="" class="wp-image-181" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-1024x898.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-300x263.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded-768x674.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline1-vs-experiment1-ttf-excluded.png 1302w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code> 14.182% worse than custom condition polling when TTF (time to first byte, irrelevant for experiment) deducted from measured latencies</figcaption></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">baseline 2 VS experiment 2</h2>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="873" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-1024x873.png" alt="" class="wp-image-182" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-1024x873.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-300x256.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-768x655.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2.png 1301w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code> 2.665% worse than custom condition polling. Additionally timeouts went up from 3.5% to 6%</figcaption></figure>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="898" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-1024x898.png" alt="" class="wp-image-183" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-1024x898.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-300x263.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded-768x673.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-baseline2-vs-experiment2-ttf-excluded.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><code>Page.waitForCondition</code>17.427% worse than custom condition polling when TTF (time to first byte, irrelevant for experiment) deducted from measured latencies</figcaption></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Let&#8217;s increase custom condition polling <code>retryWait</code> and compare results again</h1>



<h2 class="wp-block-heading">Results should be closer to <code>Page.waitForCondition</code>, right? Right?</h2>



<p class="wp-block-paragraph">Intuitively with increased wait before another condition poll, results should get less accurate. And this way get closer to less accurate playwright&#8217;s <code>Page.waitForCondition</code>. Right?</p>



<p class="wp-block-paragraph">Nope, in reality increasing retryWait up to 40ms makes no difference.</p>



<h2 class="wp-block-heading">Custom polling with <code>retryWait</code> <code>10 ms</code> VS longer <code>retryWait</code></h2>



<p class="wp-block-paragraph">10 ms VS 20 ms &#8211; no difference</p>



<p class="wp-block-paragraph">10 ms VS 40 ms &#8211; no difference</p>



<p class="wp-block-paragraph">10 ms VS 80 ms &#8211; 2.644% worse</p>



<p class="wp-block-paragraph">So there is difference noticeable in measurements when increasing polling retry wait from 10 ms to 80 ms. Sweet spot is 40 ms</p>



<h1 class="wp-block-heading">Why is there a difference between custom condition polling and <code>Page.waitForCondition</code>?</h1>



<p class="wp-block-paragraph">Let&#8217;s take a look at playwright&#8217;s implementation for <code>Page.waitForCondition</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="// com.microsoft.playwright.Page#waitForCondition(java.util.function.BooleanSupplier)
 @Override
 public void waitForCondition(BooleanSupplier predicate, WaitForConditionOptions options) {
   List&lt;Waitable&lt;Void&gt;&gt; waitables = new ArrayList&lt;&gt;();
   waitables.add(createWaitForCloseHelper());
   waitables.add(createWaitableTimeout(options == null ? null : options.timeout));
   waitables.add(new WaitablePredicate&lt;&gt;(predicate));
   runUntil(() -&gt; {}, new WaitableRace&lt;&gt;(waitables));
 }

 // com.microsoft.playwright.impl.ChannelOwner#runUntil
 &lt;T&gt; T runUntil(Runnable code, Waitable&lt;T&gt; waitable) {
   try {
     code.run();
     while (!waitable.isDone()) {
       connection.processOneMessage();
     }
     return waitable.get();
   } finally {
     waitable.dispose();
   }
 }

 // com.microsoft.playwright.impl.Connection#processOneMessage
 void processOneMessage() {
   JsonObject message = transport.poll(Duration.ofMillis(10));
   if (message == null) {
     return;
   }
   Gson gson = gson();
   Message messageObj = gson.fromJson(message, Message.class);
   dispatch(messageObj);
 }" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// com.microsoft.playwright.Page#waitForCondition(java.util.function.BooleanSupplier)</span></span>
<span class="line"><span style="color: #D8DEE9FF"> @Override</span></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> void </span><span style="color: #88C0D0">waitForCondition</span><span style="color: #D8DEE9FF">(BooleanSupplier predicate, WaitForConditionOptions options) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">   List</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">Waitable</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">Void</span><span style="color: #81A1C1">&gt;&gt;</span><span style="color: #D8DEE9FF"> waitables </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new ArrayList</span><span style="color: #81A1C1">&lt;&gt;</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   waitables.</span><span style="color: #88C0D0">add</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">createWaitForCloseHelper</span><span style="color: #D8DEE9FF">());</span></span>
<span class="line"><span style="color: #D8DEE9FF">   waitables.</span><span style="color: #88C0D0">add</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">createWaitableTimeout</span><span style="color: #D8DEE9FF">(options </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> ? </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> : options.timeout));</span></span>
<span class="line"><span style="color: #D8DEE9FF">   waitables.</span><span style="color: #88C0D0">add</span><span style="color: #D8DEE9FF">(new WaitablePredicate</span><span style="color: #81A1C1">&lt;&gt;</span><span style="color: #D8DEE9FF">(predicate));</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #88C0D0">runUntil</span><span style="color: #D8DEE9FF">(() </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> {}, new WaitableRace</span><span style="color: #81A1C1">&lt;&gt;</span><span style="color: #D8DEE9FF">(waitables));</span></span>
<span class="line"><span style="color: #D8DEE9FF"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// com.microsoft.playwright.impl.ChannelOwner#runUntil</span></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> T </span><span style="color: #88C0D0">runUntil</span><span style="color: #D8DEE9FF">(Runnable code, Waitable</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> waitable) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> {</span></span>
<span class="line"><span style="color: #D8DEE9FF">     code.</span><span style="color: #88C0D0">run</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">while</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">waitable.</span><span style="color: #88C0D0">isDone</span><span style="color: #D8DEE9FF">()) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       connection.</span><span style="color: #88C0D0">processOneMessage</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">     }</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> waitable.</span><span style="color: #88C0D0">get</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   } </span><span style="color: #81A1C1">finally</span><span style="color: #D8DEE9FF"> {</span></span>
<span class="line"><span style="color: #D8DEE9FF">     waitable.</span><span style="color: #88C0D0">dispose</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// com.microsoft.playwright.impl.Connection#processOneMessage</span></span>
<span class="line"><span style="color: #D8DEE9FF"> void </span><span style="color: #88C0D0">processOneMessage</span><span style="color: #D8DEE9FF">() {</span></span>
<span class="line"><span style="color: #D8DEE9FF">   JsonObject message </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> transport.</span><span style="color: #88C0D0">poll</span><span style="color: #D8DEE9FF">(Duration.</span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF">));</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (message </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF">) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   Gson gson </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">gson</span><span style="color: #D8DEE9FF">();</span></span>
<span class="line"><span style="color: #D8DEE9FF">   Message messageObj </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> gson.</span><span style="color: #88C0D0">fromJson</span><span style="color: #D8DEE9FF">(message, Message.class);</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #88C0D0">dispatch</span><span style="color: #D8DEE9FF">(messageObj);</span></span>
<span class="line"><span style="color: #D8DEE9FF"> }</span></span></code></pre></div>



<p class="wp-block-paragraph">Under the hood in java implementation, playwright uses condition polling every 10 ms. The same interval which was used in custom polling. So why it has worse accuracy than custom condition polling? Source code at this level does not answer the question and that might be a question to playwright dev team.</p>



<h1 class="wp-block-heading">How about <code>Page.locator</code> VS <code>Page.isVisible</code> polling?</h1>



<p class="wp-block-paragraph">After I saw it makes a difference when polling is used for more complex scenarios I thought &#8220;<em>let&#8217;s also checked if it there is difference for plain locators like <code>Page.getByText</code></em>.</p>



<p class="wp-block-paragraph">baseline</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Locator.WaitForOptions
import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   page.navigate(&quot;https://github.com/&quot;, NavigateOptions().setWaitUntil(COMMIT))
   page.getByText(&quot;Sign up for GitHub&quot;).first().waitFor(WaitForOptions().setTimeout(2000.0))
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Locator.WaitForOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com/&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2000.0</span><span style="color: #D8DEE9FF">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre></div>



<p class="wp-block-paragraph">experiment</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#2e3440"><span style="background:#c8d0e0;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#2e3440">Kotlin</span></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Page
import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.TimeoutError
import com.microsoft.playwright.options.WaitUntilState.COMMIT
import com.performance.explainer.api.waiter.NonBlockingConditionPolling
import java.time.Duration.ofMillis
import java.time.Duration.ofSeconds
import java.util.function.Consumer

Consumer&lt;Page&gt; { page -&gt;
   val conditionPolling = NonBlockingConditionPolling.default()
   page.navigate(&quot;https://github.com/&quot;, NavigateOptions().setWaitUntil(COMMIT))
   val isButtonVisible = conditionPolling.await(retryWait = ofMillis(40), timeout = ofSeconds(2)) {
       page.isVisible(&quot;text=Sign up for GitHub&quot;)
   }
   if (!isButtonVisible) {
       throw TimeoutError(&quot;Button not visible&quot;)
   }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.TimeoutError</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.performance.explainer.api.waiter.NonBlockingConditionPolling</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofMillis</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.time.Duration.ofSeconds</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> java.util.function.Consumer</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">Consumer</span><span style="color: #D8DEE9FF">&lt;Page&gt; { page </span><span style="color: #81A1C1">-&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> conditionPolling </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> NonBlockingConditionPolling.</span><span style="color: #88C0D0">default</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">   page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com/&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT))</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> isButtonVisible </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> conditionPolling.</span><span style="color: #88C0D0">await</span><span style="color: #D8DEE9FF">(retryWait </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofMillis</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">40</span><span style="color: #D8DEE9FF">), timeout </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ofSeconds</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">)) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       page.</span><span style="color: #88C0D0">isVisible</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;text=Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">isButtonVisible) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">TimeoutError</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Button not visible&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">   }</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre></div>



<figure class="wp-block-image size-large is-style-zoooom"><img decoding="async" width="1024" height="630" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-1024x630.png" alt="" class="wp-image-185" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-1024x630.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-300x185.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms-768x473.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/05/comparison-github-locator-vs-polling-40ms.png 1300w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption"><strong>Uff, no difference</strong> between using <code>Page.getByText</code> vs custom condition polling</figcaption></figure>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<h1 class="wp-block-heading">Summary</h1>



<p class="wp-block-paragraph">I started with checking if playwright <code>Page.waitForCondition</code> replaced by custom condition polling every 10ms makes a difference. And it&#8217;s faster which means you get more accurate results with this approach.</p>



<p class="wp-block-paragraph">Then I also measured 20ms, 40ms, 80ms custom condition polling interval. Up to 40ms there is no difference in measured latencies and it&#8217;s a sweet spot for retries between condition check.</p>



<h1 class="wp-block-heading">Conclusion</h1>



<p class="wp-block-paragraph">If you want higher accuracy of web app performance measurement, use custom condition polling 40ms instead of playwright <code>Page.waitForCondition</code></p><p>The post <a href="http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/">Want to have better accuracy of performance benchmark? Stop using playwright Page.waitForCondition</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/want-to-have-better-accuracy-of-performance-benchmark-stop-using-playwright-page-waitforcondition/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/theathletesfoot-measurement-example.webm" length="84722" type="video/webm" />
<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blogmarketingacademy-measurement-example.webm" length="134053" type="video/webm" />

			</item>
		<item>
		<title>How to make performance benchmark 530% worse by relying on web app automation defaults?</title>
		<link>http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults</link>
					<comments>http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/#comments</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Sun, 05 May 2024 12:26:21 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[DOMContentLoaded]]></category>
		<category><![CDATA[end-to-end]]></category>
		<category><![CDATA[playwright]]></category>
		<category><![CDATA[selenium]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=112</guid>

					<description><![CDATA[<p>Let&#8217;s start with a necessary definitions: what are DOMContentLoaded and load events in the web browser? DOMContentLoaded According to docs when browser fires this event it means load event When browser fires load event it means DOM content is fully loaded. Browser has finished Both events are NOT about REST calls. And both are not [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/">How to make performance benchmark 530% worse by relying on web app automation defaults?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Let&#8217;s start with a necessary definitions: what are <code>DOMContentLoaded</code> and <code>load</code> events in the web browser?</h2>



<h3 class="wp-block-heading"><code>DOMContentLoaded</code></h3>



<p class="wp-block-paragraph">According to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event">docs</a> when browser fires this event it means</p>



<ul class="wp-block-list">
<li>DOM has been completely parsed</li>



<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer"><code>&lt;script defer src="…"&gt;</code></a>&nbsp;and&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#module"><code>&lt;script type="module"&gt;</code></a> has been loaded</li>



<li>async script, images, frames might not be loaded yet</li>
</ul>



<h3 class="wp-block-heading"><code>load event</code></h3>



<p class="wp-block-paragraph">When browser fires <code>load event</code> it means DOM content is fully loaded. Browser has finished</p>



<ul class="wp-block-list">
<li>executing all asynchronous resources from the DOM tree</li>



<li>loading images, frames etc.</li>



<li>it comes always after <code>DOMContentLoaded</code></li>
</ul>



<p class="wp-block-paragraph">Both events are NOT about REST calls. And both are not relevant to the user browsing the website.</p>



<h2 class="wp-block-heading">What do web apps automation tools like playwright and selenium do by default?</h2>



<p class="wp-block-paragraph">Take a look at following lines of code measuring navigating to a page and waiting for a button to be visible.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1359" height="607" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button.png" alt="" class="wp-image-116" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button.png 1359w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button-300x134.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button-1024x457.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-button-768x343.png 768w" sizes="(max-width: 1359px) 100vw, 1359px" /></figure>



<p class="wp-block-paragraph">using playwright</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val millisBefore = System.currentTimeMillis()
page.navigate(&quot;https://github.com&quot;)
page.getByText(&quot;Try Now&quot;).waitFor()
val latency = System.currentTimeMillis() - millisBefore" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> millisBefore </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Try Now&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> latency </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> millisBefore</span></span></code></pre></div>



<p class="wp-block-paragraph">or doing the same with selenium</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val millisBefore = System.currentTimeMillis()
driver.navigate().to(&quot;https://github.com&quot;)
WebDriverWait(driver, 30, 100).until(ExpectedConditions.visibilityOfElementLocated(By.linkText(&quot;Sign up for GitHub&quot;)))
val latency = System.currentTimeMillis() - millisBefore" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> millisBefore </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">driver.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">to</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line cbp-no-blur"><span style="color: #88C0D0">WebDriverWait</span><span style="color: #D8DEE9FF">(driver, </span><span style="color: #B48EAD">30</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">100</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">until</span><span style="color: #D8DEE9FF">(ExpectedConditions.</span><span style="color: #88C0D0">visibilityOfElementLocated</span><span style="color: #D8DEE9FF">(By.</span><span style="color: #88C0D0">linkText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">)))</span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> latency </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> System.</span><span style="color: #88C0D0">currentTimeMillis</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> millisBefore</span></span></code></pre></div>



<p class="wp-block-paragraph">When you navigate to a page, by default those automation tools wait for <code>load</code> event. So the line with locator is not executed until then.</p>



<p class="wp-block-paragraph">You might think <em>Pff, so what. It&#8217;s just a technical detail</em></p>



<p class="wp-block-paragraph">It is indeed a technical detail but it impacts performance benchmark <mark>dramatically</mark>.</p>



<h2 class="wp-block-heading">Waiting for <code>DOMContentLoaded</code> VS <code>load</code> VS <code>NOT waiting for any event</code>. Fight!</h2>



<p class="wp-block-paragraph">Let&#8217;s measure how quickly user can see github call to action button.</p>



<h3 class="wp-block-heading">Baseline benchmark &#8211; wait for <code>load</code> event</h3>



<p class="wp-block-paragraph">Take 1000 samples. Wait for <code>load</code> event, then wait for github call to action button</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// only most important code shown here. Processing measurements is out of the scope
page.navigate(&quot;https://github.com&quot;)
page.getByText(&quot;Sign up for GitHub&quot;).first().waitFor(WaitForOptions().setTimeout(2000.0))" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// only most important code shown here. Processing measurements is out of the scope</span></span>
<span class="line"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">getByText</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;Sign up for GitHub&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">(</span><span style="color: #88C0D0">WaitForOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setTimeout</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">2000.0</span><span style="color: #D8DEE9FF">))</span></span></code></pre></div>



<p class="wp-block-paragraph">It looks like this</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1303" height="885" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.png" alt="" class="wp-image-117" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.png 1303w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95-300x204.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95-1024x696.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95-768x522.png 768w" sizes="(max-width: 1303px) 100vw, 1303px" /><figcaption class="wp-element-caption">wait for <code>load</code> event` at percentile 95, button measured as visible after 914 ms</figcaption></figure>



<h4 class="wp-block-heading">How to interpret the video of web app measurement?</h4>



<p class="wp-block-paragraph">Playwright video recording is imperfect. Video frame is often not aligned with what was seen in web browser. I confirmed that with modifying the measured page after measurement end.</p>



<p class="wp-block-paragraph">During recording web app, right after measurement end timer is added to the page. This way it doesn&#8217;t interfere with measurement and helps to see when measurement has actually finished on the video.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(2 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="const measuerementEndMillis = ..; // depends on the measuerement
const timerDiv = document.createElement('div');
timerDiv.style.position = 'fixed';
timerDiv.style.top = '0';
timerDiv.style.left = '0';
timerDiv.style.opacity = '0.8';
timerDiv.style.fontSize = '14px';
timerDiv.style.backgroundColor = 'yellow';
timerDiv.style.zIndex = '10000';
document.body.appendChild(timerDiv);

setInterval(function() {
  const now = performance.now();
  timerDiv.textContent = now.toFixed(0) + ' ms, ' + (now - measurementEndMillis).toFixed(0) + ' ms';
}, 20);" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">measuerementEndMillis</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">..</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// depends on the measuerement</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">timerDiv</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">document</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">createElement</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">div</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">position</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">fixed</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">top</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">left</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">opacity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0.8</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">fontSize</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">14px</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">backgroundColor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">yellow</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">style</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">zIndex</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">10000</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9">document</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">body</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">appendChild</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">timerDiv</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">setInterval</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">function</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">now</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">performance</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">now</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #D8DEE9">timerDiv</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">textContent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">now</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toFixed</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C"> ms, </span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">now</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">measurementEndMillis</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toFixed</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C"> ms</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">},</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">20</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span></code></pre></div>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="556" height="140" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-timer.png" alt="" class="wp-image-136" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-timer.png 556w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-github-timer-300x76.png 300w" sizes="(max-width: 556px) 100vw, 556px" /><figcaption class="wp-element-caption">X, Y<br>X &#8211; milliseconds since time origin (page started loading)<br>Y &#8211; milliseconds since measurement end. Timer is added after locator is satisfied so it doesn&#8217;t disturb the measurement</figcaption></figure>
</div>


<p class="wp-block-paragraph"><strong>When measuring very fast page like github.com landing page, you&#8217;re often going to see a blank page and right after that frames recorded some time after measurement end (signup button visible). Frames are lost. Based on that you should treat every moment of playwright video as possibly inaccurate</strong>.</p>



<p class="wp-block-paragraph">Having said that, let&#8217;s move on.</p>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.webm"></video><figcaption class="wp-element-caption">wait for <code>load</code> event at percentile 95, button measured as visible after 914 ms. Mind the significant part of the video with button visible, yet measurement is not finished</figcaption></figure>



<h3 class="wp-block-heading">Experiment benchmark 1 &#8211; no waiting</h3>



<p class="wp-block-paragraph">Take 1000 samples. Don&#8217;t wait for any event, wait for github call to action button</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.COMMIT

// from playwright docs WaitUntilState.COMMIT:
// consider operation to be finished when network response is received and the document started loading
page.navigate(&quot;https://github.com&quot;, NavigateOptions().setWaitUntil(COMMIT))
page.locator(&quot;#summary-val&quot;).first().waitFor()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.COMMIT</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// from playwright docs WaitUntilState.COMMIT:</span></span>
<span class="line"><span style="color: #616E88">// consider operation to be finished when network response is received and the document started loading</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(COMMIT))</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#summary-val&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<p class="wp-block-paragraph">In selenium you have to opt out from waiting for <code>DOMCompleteEvent</code> this way</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val options = new ChromeOptions()
options.setPageLoadStrategy(PageLoadStrategy.NONE)    
val driver = new ChromeDriver(options)
driver.navigate(..).to()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> options </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeOptions</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">options.</span><span style="color: #88C0D0">setPageLoadStrategy</span><span style="color: #D8DEE9FF">(PageLoadStrategy.NONE)    </span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> driver </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeDriver</span><span style="color: #D8DEE9FF">(options)</span></span>
<span class="line"><span style="color: #D8DEE9FF">driver.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">to</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1301" height="887" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.png" alt="" class="wp-image-119" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.png 1301w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95-300x205.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95-1024x698.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95-768x524.png 768w" sizes="(max-width: 1301px) 100vw, 1301px" /><figcaption class="wp-element-caption">no wait for event at percentile 95, button measured as visible after 156 ms</figcaption></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.webm"></video><figcaption class="wp-element-caption">no wait for event at percentile 95, button measured as visible after 156 ms</figcaption></figure>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">Experiment benchmark 2 &#8211; wait for <code>DOMContentLoaded</code> event</h3>



<p class="wp-block-paragraph">Take 1000 samples. Wait for <code>DOMContentLoaded</code>, wait wait for github call to action button</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers cbp-blur-enabled cbp-unblur-on-hover" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import com.microsoft.playwright.Page.NavigateOptions
import com.microsoft.playwright.options.WaitUntilState.DOMCONTENTLOADED

// from playwright docs WaitUntilState.DOMCONTENTLOADED:
// consider operation to be finished when the DOMContentLoaded event is fired.
page.navigate(&quot;https://github.com&quot;, NavigateOptions().setWaitUntil(DOMCONTENTLOADED))
page.locator(&quot;#summary-val&quot;).first().waitFor()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.Page.NavigateOptions</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> com.microsoft.playwright.options.WaitUntilState.DOMCONTENTLOADED</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// from playwright docs WaitUntilState.DOMCONTENTLOADED:</span></span>
<span class="line"><span style="color: #616E88">// consider operation to be finished when the DOMContentLoaded event is fired.</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;https://github.com&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">NavigateOptions</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">setWaitUntil</span><span style="color: #D8DEE9FF">(DOMCONTENTLOADED))</span></span>
<span class="line cbp-no-blur"><span style="color: #D8DEE9FF">page.</span><span style="color: #88C0D0">locator</span><span style="color: #D8DEE9FF">(</span><span style="color: #A3BE8C">&quot;#summary-val&quot;</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">first</span><span style="color: #D8DEE9FF">().</span><span style="color: #88C0D0">waitFor</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<p class="wp-block-paragraph">In selenium you have to opt out from waiting for <code>DOMContentLoaded</code> event this way</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:14px;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#d8dee9ff;--cbp-line-number-width:calc(1 * 0.6 * 14px);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="val options = new ChromeOptions()
options.setPageLoadStrategy(PageLoadStrategy.EAGER)    
val driver = new ChromeDriver(options)
driver.navigate(..).to()" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> options </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeOptions</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">options.</span><span style="color: #88C0D0">setPageLoadStrategy</span><span style="color: #D8DEE9FF">(PageLoadStrategy.EAGER)    </span></span>
<span class="line"><span style="color: #81A1C1">val</span><span style="color: #D8DEE9FF"> driver </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> new </span><span style="color: #88C0D0">ChromeDriver</span><span style="color: #D8DEE9FF">(options)</span></span>
<span class="line"><span style="color: #D8DEE9FF">driver.</span><span style="color: #88C0D0">navigate</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">).</span><span style="color: #88C0D0">to</span><span style="color: #D8DEE9FF">()</span></span></code></pre></div>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1303" height="887" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.png" alt="" class="wp-image-121" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.png 1303w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95-300x204.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95-1024x697.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95-768x523.png 768w" sizes="(max-width: 1303px) 100vw, 1303px" /><figcaption class="wp-element-caption">wait for <code>DOMContentLoaded</code> event at percentile 95, button measured as visible after 802 ms</figcaption></figure>



<figure class="wp-block-video"><video height="450" style="aspect-ratio: 800 / 450;" width="800" controls src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.webm"></video><figcaption class="wp-element-caption">wait for <code>DOMContentLoaded</code> event at percentile 95, button measured as visible after 802 ms. Mind the significant part of the video with button visible, yet measurement is not finished</figcaption></figure>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">And now let&#8217;s compare the results</h3>



<h4 class="wp-block-heading">P95 comparison</h4>



<figure class="wp-block-table"><table><tbody><tr><td>no waiting</td><td>wait for <code>DOMContentLoaded</code></td><td>wait for <code>load</code> event</td></tr><tr><td>156 ms</td><td>802 ms</td><td>914 ms</td></tr></tbody></table><figcaption class="wp-element-caption">95th percentile  across different type of loading website in playwright/selenium</figcaption></figure>



<p class="wp-block-paragraph">Percentiles do not answer the question <em>how distribution compares</em>? Down the line you&#8217;re going to see <a href="https://en.wikipedia.org/wiki/Hodges%E2%80%93Lehmann_estimator" target="_blank" rel="noopener" title="">Hodges-Lehman estimator</a></p>



<h4 class="wp-block-heading">What hypotheses are we verifying here?</h4>



<ul class="wp-block-list">
<li><code>load</code> event
<ul class="wp-block-list">
<li>(baseline) null hypothesis: waiting for <code>load</code> event has completion has no performance impact</li>



<li>(experiment 1) alternative hypothesis: waiting for <code>load</code>event has impact on measured latencies</li>
</ul>
</li>



<li><code>DOMContentLoaded</code> event
<ul class="wp-block-list">
<li>(baseline) null hypothesis: waiting for <code>DOMContentLoad</code> event has has no impact on measured latencies</li>



<li>(experiment 2) alternative hypothesis: waiting for <code>DOMContentLoad</code>event has impact on measured latencies</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">Waiting for <code>load</code> event VS not waiting for any event</h3>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1303" height="429" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait.png" alt="" class="wp-image-124" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait.png 1303w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait-300x99.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait-1024x337.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-load-vs-no-wait-768x253.png 768w" sizes="(max-width: 1303px) 100vw, 1303px" /><figcaption class="wp-element-caption">83.829% improvement when not waiting for any browser event</figcaption></figure>



<p class="wp-block-paragraph">Title of this blog post says there is 530% performance degradation when you rely on browser automation defaults. So where is it? When you reverse baseline VS experiment, there it is</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1301" height="279" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load.png" alt="" class="wp-image-125" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load.png 1301w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-300x64.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-1024x220.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-768x165.png 768w" sizes="(max-width: 1301px) 100vw, 1301px" /><figcaption class="wp-element-caption">Null hypothesis is rejected, waiting for <code>load</code> event comes with bigger latencies</figcaption></figure>



<p class="wp-block-paragraph">Null hypothesis is rejected, waiting for <code>load</code> event comes with bigger latencies</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1302" height="312" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb.png" alt="" class="wp-image-126" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb.png 1302w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb-300x72.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb-1024x245.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-no-wait-vs-wait-for-load-after-ttfb-768x184.png 768w" sizes="(max-width: 1302px) 100vw, 1302px" /><figcaption class="wp-element-caption">Time to first byte is irrelevant for hypothesis verification. When you remove it from measured latencies it unfolds more accurate impact on frontend part.</figcaption></figure>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading">Waiting for <code>DOMContentLoaded</code> VS not waiting for any event</h3>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1300" height="279" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait.png" alt="" class="wp-image-128" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait.png 1300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait-300x64.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait-1024x220.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-no-wait-768x165.png 768w" sizes="(max-width: 1300px) 100vw, 1300px" /><figcaption class="wp-element-caption">79.642% smaller latencies when not waiting for any browser event</figcaption></figure>



<p class="wp-block-paragraph">Null hypothesis is rejected, waiting for <code>DOMContentLoaded </code>increases measured latencies</p>



<h3 class="wp-block-heading">Waiting for <code>DOMContentLoaded</code> VS waiting for <code>load</code> event</h3>



<p class="wp-block-paragraph">Having 2 experiments we can compare them too. Since <code>load</code> event comes always after <code>DOMContentLoaded</code> it&#8217;s expected to see bigger latencies which is reflected in measurements</p>



<figure class="wp-block-image size-full is-style-zoooom"><img decoding="async" width="1300" height="278" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load.png" alt="" class="wp-image-129" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load.png 1300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load-300x64.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load-1024x219.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-comparison-wait-for-DOMContentLoaded-vs-wait-for-load-768x164.png 768w" sizes="(max-width: 1300px) 100vw, 1300px" /></figure>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Based on measurements, what are undesired effects of waiting for <code>DOMContentLoad</code> or <code>load</code> event?</h2>



<ul class="wp-block-list">
<li>To understand better following part take a look at difference between accuracy and precision</li>
</ul>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="764" height="794" src="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-Accuracy-vs-Precision.png" alt="" class="wp-image-130" style="width:507px;height:auto" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-Accuracy-vs-Precision.png 764w, http://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-Accuracy-vs-Precision-289x300.png 289w" sizes="(max-width: 764px) 100vw, 764px" /></figure>



<ul class="wp-block-list">
<li>The undesired effects</li>



<li>Measuring something neither reflecting user experience nor relevant to user experience. User sees a button and clicks a button regardless of events in the browser
<ul class="wp-block-list">
<li>No user ever thought &#8220;<em>I politely wish this site had dom completion faster</em>&#8220;. User thinks &#8220;<em>Why the f^%@ is this button not working yet?!!??</em>&#8220;</li>
</ul>
</li>



<li>(much) longer benchmarks
<ul class="wp-block-list">
<li>for ones that aim for measurement count, e.g. we want to have 200 samples</li>
</ul>
</li>



<li>Less precise benchmarks
<ul class="wp-block-list">
<li>for ones that aim for duration, e.g. running for 10 minutes</li>



<li>because you will get less samples</li>
</ul>
</li>



<li>Less accurate benchmarks
<ul class="wp-block-list">
<li>because you&#8217;re not measuring what user experiencies</li>



<li>As a result system under benchmark might actually meet some acceptance criteria, while measurements show it&#8217;s not</li>
</ul>
</li>



<li>Disturbs calculating correlations and figuring out frontend latency causation</li>
</ul>



<h2 class="wp-block-heading">Side effects of not waiting for <code>DOMContentLoaded</code> or <code>loaded</code> event</h2>



<p class="wp-block-paragraph">User can click a visible button, that does not react on clicks yet. Which actually is a reality <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">However that complicates measurements. Frameworks do not check if a button has event listeners. It often requires polling for a desired button state. e.g. await condition click and check if expected message is visible</p>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">I&#8217;ve taken thousands of measurements of a few different sites, not only github. Systems behave the same. Waiting for browser events makes measured latencies inaccurate. It might be 10% away from real user experience, it might be 530%.</p>



<p class="wp-block-paragraph">It&#8217;s non-zero, reproducible and deterministic. So if you already made an effort to make synthetic performance benchmarks, I&#8217;d recommend putting an effort also in increasing their accuracy.</p>



<h3 class="wp-block-heading">Consider changing default options for browser automation</h3>



<p class="wp-block-paragraph">For playwright and selenium (and whatever browser automation tools there are) you should opt out from waiting for any browser events if you&#8217;re using those tools for performance measurements.</p>



<figure class="wp-block-table"><table><tbody><tr><td>Option</td><td>Selenium</td><td>Playwright</td></tr><tr><td>wait for response received (<strong>best for performance benchmarks</strong>)</td><td>NONE</td><td>COMMIT</td></tr><tr><td>wait for load event (<strong>default</strong>)</td><td>NORMAL</td><td>LOAD</td></tr><tr><td>wait for DOMContentLoaded event (another option)</td><td>EAGER</td><td>DOMCONTENTLOADED</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"></p><p>The post <a href="http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/">How to make performance benchmark 530% worse by relying on web app automation defaults?</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/how-to-make-performance-benchmark-530-worse-by-relying-on-web-app-automation-defaults/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-load-event-p95.webm" length="43031" type="video/webm" />
<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-no-wait-p95.webm" length="41413" type="video/webm" />
<enclosure url="https://deploymenteveryday.com/wp-content/uploads/2024/05/blog_post_should-you-wait-for-DOMContentLoaded-wait-for-DOMContentLoaded-p95.webm" length="41614" type="video/webm" />

			</item>
		<item>
		<title>How to use percentile rank to measure performance</title>
		<link>http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-use-percentile-rank-to-measure-performance</link>
					<comments>http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Sat, 20 Apr 2024 13:52:04 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[estimator]]></category>
		<category><![CDATA[ui-benchmark]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=104</guid>

					<description><![CDATA[<p>If you want to have a simple yet meaningful criteria that answers the question Is my product fast enough? this blog post is for you. First things first. What&#8217;s the goal of a UI measurement? Who consumes UI? Humans from planet Earth, of course. They get annoyed, frustrated and want to throw away your website [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/">How to use percentile rank to measure performance</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">If you want to have a simple yet meaningful criteria that answers the question <code>Is my product fast enough?</code> this blog post is for you.</h1>



<h1 class="wp-block-heading">First things first. What&#8217;s the goal of a UI measurement?</h1>



<p class="wp-block-paragraph">Who consumes UI? Humans from planet Earth, of course. They get annoyed, frustrated and want to throw away your website out of the window when it&#8217;s too slow. They also think very neat things about your product when they are waiting too long.</p>



<p class="wp-block-paragraph">So the ultimate goal of measuring UI is to know if/what parts of the system should be improved in order to keep users happy.</p>



<h1 class="wp-block-heading">What question should performance pass / fail criteria answer?</h1>



<p class="wp-block-paragraph">It should answer the question: &#8220;<em>Are users happy using my product?</em>&#8220;.</p>



<h2 class="wp-block-heading">Why happy user is that important? And what are consequences of having frustrated users because of poor performance?</h2>



<p class="wp-block-paragraph">You&#8217;d better keep users happy by keeping your site fast. Not only you users suffer. Your business suffers. The longer you&#8217;re making users to wait, the more</p>



<ul class="wp-block-list">
<li>bounce rate goes up</li>



<li>you&#8217;re losing traffic from organic search as search engine prefers faster sites
<ul class="wp-block-list">
<li>It&#8217;s a real deal. I already spoke to people having this particular problem. Google changed the rules (web vitals) and whole team needed to work on performance to bring back lost organic traffic</li>
</ul>
</li>



<li>and for systems that companies use internally daily
<ul class="wp-block-list">
<li>users will start to look for alternative solutions that frustrate them less</li>



<li>or have a grumpy-cat face all day</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">When system passes acceptance criteria it means particular part of your system is fast enough. It means there aren&#8217;t too many frustrated users. Product does not need to be fixed in this area.</p>



<p class="wp-block-paragraph">When system does not pass acceptance criteria it means it&#8217;s too slow. Which means there are too many frustrated users. Product needs to be fixed in this area.</p>



<h1 class="wp-block-heading">What&#8217;s the perfect threshold for UI pass / fail acceptance criteria?</h1>



<p class="wp-block-paragraph">0 ms so no one waits for anything <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Jokes aside, let&#8217;s see what research says about latencies.</p>



<h2 class="wp-block-heading">What research says about latency and user frustration</h2>



<ul class="wp-block-list">
<li>https://hpbn.co/primer-on-web-performance/#speed-performance-and-human-perception</li>



<li>Latency longer than 1s causes likely mental context switch. And bounce rate goes up</li>



<li>And that might be our frustration point</li>
</ul>



<p class="wp-block-paragraph">Even if a particular part of your UI is far away from latency 1s, it might be a good, ambitious goal that you can track. Don&#8217;t lower the expectations just because system will not pass the criteria. You can always measure the gap to passing the criteria and progress on closing the gap.</p>



<h1 class="wp-block-heading">Measuring user happiness and frustration is nothing new and it works well</h1>



<p class="wp-block-paragraph">There already are well thought concepts. For example</p>



<h2 class="wp-block-heading"><code>APDEX</code></h2>



<ul class="wp-block-list">
<li>https://docs.newrelic.com/docs/apm/new-relic-apm/apdex/apdex-measure-user-satisfaction/</li>



<li>User satisfaction based on T, below which user is satisfied
<ul class="wp-block-list">
<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f603.png" alt="😃" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Satisfied, latency <code>&lt;0, T]</code></li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f610.png" alt="😐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Tolerated, latency <code>&lt;T, 4T]</code></li>



<li><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f621.png" alt="😡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Frustrated, latency <code>&lt;4T, infinity)</code> or <code>error</code></li>
</ul>
</li>



<li>Score [0, 1]
<ul class="wp-block-list">
<li>0 &#8211; all frustrated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f621.png" alt="😡" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>1 &#8211; all satisfied <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f603.png" alt="😃" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>(number of requests within satisfied range + (number of requests within tolerated range) / 2) / total number of requests</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><code>APDEX</code> makes sense. However I will explain percentile rank as even simpler and as powerful concept.</p>



<p class="wp-block-paragraph">You might have also heard about percentiles, so let&#8217;s compare percentiles and percentile rank in practical terms.</p>



<h2 class="wp-block-heading">Percentiles. Usually P90, P95 or P99</h2>



<p class="wp-block-paragraph">What&#8217;s the idea?</p>



<p class="wp-block-paragraph">Sort the measured latencies array ascending. P90 is index in this array at 90th %.</p>



<p class="wp-block-paragraph">P90 = latencies[latencies.size * 0.9]</p>



<p class="wp-block-paragraph">P95 = latencies[latencies.size * 0.95]</p>



<p class="wp-block-paragraph">etc</p>



<p class="wp-block-paragraph">Using high percentiles (P90-P99.9) you can find out the slowest latencies user expierence.</p>



<p class="wp-block-paragraph">Calculating percentiles requires whole population (latencies sorted ascending). It&#8217;s not applicable for live monitoring of systems as you have to store every single measured latency.</p>



<h2 class="wp-block-heading">Percentile rank</h2>



<p class="wp-block-paragraph">Statistical meaning for PR(n)</p>



<ul class="wp-block-list">
<li>How many % of other values are lower than <em>n</em></li>



<li>Llike on exam: how many % of other students you&#8217;ve beaten with your score</li>
</ul>



<p class="wp-block-paragraph">It doesn&#8217;t require whole population to calculate, you can count it even on stream. Just increment counter of the passed threshold(s).</p>



<p class="wp-block-paragraph">Implementation is simple</p>



<pre class="wp-block-code"><code>val badCount = 0
val goodCount = 0
val threshold = 1000
latencies.forEach {
 if (it &amp;lt; threshold) goodCount++ else badCount++
}
val percentileRankPercent = goodCount * 100.0 / (badCount + goodCount)
</code></pre>



<h1 class="wp-block-heading">Why not just use percentiles and P90?</h1>



<p class="wp-block-paragraph">P90 means <code>what's the value at 90%th index of sorted array?</code></p>



<p class="wp-block-paragraph">PR(1s) means <code>how many latencies are below 1s?</code>. Not <code>below or equal</code> and that makes the difference.</p>



<p class="wp-block-paragraph">P90 = 1s doesn&#8217;t mean PR(1s) = 90%</p>



<p class="wp-block-paragraph">Take a look at the example. Given following, sorted array of latencies [s], 10 elements for simplicity</p>



<ul class="wp-block-list">
<li><code>0.25, 0.3, 0.6, 0.6, 0.7, 0.8, 0.85, 0.9, 1.0, 1.4</code>
<ul class="wp-block-list">
<li>P90 = 1.0</li>



<li>PR(1) = 80%</li>



<li>PR(1.1) = 90%</li>



<li>PR(1.5) = 100%</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Let&#8217;s change the example. Latencies got worse, but P90 stays the same</p>



<ul class="wp-block-list">
<li><code>0.25, 0.3, 0.6, 0.6, 0.7, 1.0, 1.0, 1.0, 1.0, 1.4</code>
<ul class="wp-block-list">
<li>P90 = 1.0</li>



<li>PR(1) = 50%</li>
</ul>
</li>



<li>As you can see, a single percentile does not say how many samples</li>
</ul>



<p class="wp-block-paragraph">Let&#8217;s change the example. Latencies got even worse, P90 stays the same</p>



<ul class="wp-block-list">
<li><code>1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.4</code>
<ul class="wp-block-list">
<li>P90 = 1.0</li>



<li>PR(1) = 0%</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">As you can see, relying on a single percentile brings a problem. For P90 anything between [0, 90%] of latencies might be as bad as P90.</p>



<p class="wp-block-paragraph">Percentiles are not designed to answer question <strong>how good/bad this latency is?</strong></p>



<h1 class="wp-block-heading">Let&#8217;s construct the pass / fail acceptance criteria based on percentile rank</h1>



<p class="wp-block-paragraph">Based on previously mentioned research and 1s as a user frustration point, let&#8217;s construct the acceptance criteria.</p>



<p class="wp-block-paragraph"><code>PR(1s) is expected to be at least 99%</code></p>



<ul class="wp-block-list">
<li>Which means &#8220;<em>At least 99% of website interactions user should experience latency below 1s</em>&#8220;</li>



<li>Which also means &#8220;<em>User can be frustrated by max 1% of website interactions</em>&#8220;</li>
</ul>



<p class="wp-block-paragraph">What if after a benchmark, criteria is NOT met?</p>



<p class="wp-block-paragraph">e.g. <code>PR(1s) = 80% and is expected to be at least 99%</code></p>



<p class="wp-block-paragraph">It means 19% of the responses are too slow. 19% of interactions needs to be fixed.</p>



<h1 class="wp-block-heading">How to measure improvement if you start with PR(1s) = 0%</h1>



<p class="wp-block-paragraph">You might be in a situation that all latencies are &gt;= 1s. 100% frustrated user. You want to improve the product by chaning something.</p>



<p class="wp-block-paragraph"><strong>Before change</strong></p>



<ul class="wp-block-list">
<li>PR(1s) = 0%</li>



<li>All measured latencies are be above 2s</li>
</ul>



<p class="wp-block-paragraph"><strong>After change, new commits added to the product</strong></p>



<ul class="wp-block-list">
<li>PR(1s) = 0%</li>



<li>All of measured latencies are above 1.5s. It is an improvement, but percentile rank hasn&#8217;t changed.</li>
</ul>



<p class="wp-block-paragraph">And you can&#8217;t just say &#8220;<em>this improvement doesn&#8217;t matter, user is still frustrated because criteria measuring user&#8217;s frustration is till unmet</em>&#8220;. Some improvements require a lot of iterative work and you need a devloop in which you notice the improvement. Even if it&#8217;s still far away from having happy user.</p>



<p class="wp-block-paragraph">To compare if there is an improvement you could use comparison benchmark with Hodges-Lehmann estimator.</p>



<ul class="wp-block-list">
<li>That requires access to every single latency from baseline and experiment</li>
</ul>



<p class="wp-block-paragraph">You could also use a simple measure which I call &#8216;wasted user time&#8217; which you want to keep at 0.</p>



<pre class="wp-block-code"><code>val thresholdMillis = 1000
val allowedDurationMillis = latencies.size * thresholdMillis
val totalDurationMillis = latencies.sumOf { it.toMillis() }
val excessiveDurationMillis = totalDurationMillis - allowedDurationMillis
val wastedUserTimePercent = excessiveDurationMillis * 100.0 / totalDurationMillis
</code></pre>



<p class="wp-block-paragraph">This way even if percentile rank hasn&#8217;t changed you can still measure the improvement.</p>



<p class="wp-block-paragraph">And that&#8217;s the exact methodology I&#8217;m using to measure webpages performance and answer questions &#8220;how to improve the system?&#8221;</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="531" src="https://deploymenteveryday.com/wp-content/uploads/2024/04/image-1024x531.png" alt="" class="wp-image-108" srcset="http://deploymenteveryday.com/wp-content/uploads/2024/04/image-1024x531.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2024/04/image-300x155.png 300w, http://deploymenteveryday.com/wp-content/uploads/2024/04/image-768x398.png 768w, http://deploymenteveryday.com/wp-content/uploads/2024/04/image.png 1318w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Real life example of Percentile Rank used to check if a website is fast enough</figcaption></figure><p>The post <a href="http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/">How to use percentile rank to measure performance</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/how-to-use-percentile-rank-to-measure-performance/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Example of 40x speedup using async-profiler recording analysis (JFR file)</title>
		<link>http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file</link>
					<comments>http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/#respond</comments>
		
		<dc:creator><![CDATA[Mikolaj Grzaslewicz]]></dc:creator>
		<pubDate>Wed, 27 Dec 2023 15:06:56 +0000</pubDate>
				<category><![CDATA[performance]]></category>
		<category><![CDATA[async profiler]]></category>
		<category><![CDATA[bottleneck analysis]]></category>
		<category><![CDATA[flamegraph]]></category>
		<category><![CDATA[JFR]]></category>
		<category><![CDATA[performance improvement]]></category>
		<guid isPermaLink="false">https://deploymenteveryday.com/?p=76</guid>

					<description><![CDATA[<p>40x performance improvement in this example is series of a few improvements combined. I was trying to check what can be improved during working on a small project. As usual, I run intellij profiler from time to time to see the hottest methods. And that was part of my development workflow aside from TDD. 2x [&#8230;]</p>
<p>The post <a href="http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/">Example of 40x speedup using async-profiler recording analysis (JFR file)</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></description>
										<content:encoded><![CDATA[<p class="wp-block-paragraph"><strong>40x performance improvement in this example is series of a few improvements combined.</strong></p>



<p class="wp-block-paragraph">I was trying to check what can be improved during working on a small project. As usual, I run intellij profiler from time to time to see the hottest methods. And that was part of my development workflow aside from <code>TDD</code>.</p>



<h1 class="wp-block-heading">2x speedup &#8211; remove String.format() usage (technical optimization)</h1>



<p class="wp-block-paragraph"><a href="https://github.com/mgrzaslewicz/set-equalizer/commit/c68b0081335d25782417b95a7e3a78bb1c4239bc" target="_blank" rel="noopener" title="">git commit</a></p>



<p class="wp-block-paragraph">Remove usage of <code>String.format("Move index %s from %s to %s", indexFrom, listFrom, listTo);</code></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="579" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-1024x579.png" alt="" class="wp-image-81" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-1024x579.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-300x170.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0-768x434.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_tostring_2022-03-31_11-49_1703190081490_0.png 1207w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">It&#8217;s not surprising it was slow, but discovering that is not obvious. Imagine you&#8217;re introduced to a new, big project &#8211; there is no way you would eyeball the code and say this is a <code>bottleneck</code> (<code>hot method</code>)</p>



<h2 class="wp-block-heading">10x speedup &#8211; replace streams with for each (technical optimization).</h2>



<p class="wp-block-paragraph"><a href="https://github.com/mgrzaslewicz/set-equalizer/commit/59a124aaf2e33f58695b76b3e5568dc6e27d10b3" target="_blank" rel="noopener" title="">git commit</a></p>



<p class="wp-block-paragraph">Replace</p>



<p class="wp-block-paragraph"><code>return calculators.stream().mapToInt(c -&gt; c.calculate(listA, listB)).sum();</code></p>



<p class="wp-block-paragraph">With</p>



<pre class="wp-block-code"><code>  int sum = 0;
  for (var calculator : calculators) {
      sum += calculator.calculate(listA, listB);
  }
  return sum;</code></pre>



<p class="wp-block-paragraph">Bottleneck (hot method) found</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="807" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-1024x807.png" alt="" class="wp-image-84" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-1024x807.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-300x236.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1-768x605.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_10x_speedup_for_each_2022-03-31_11-49_1703190434993_0-1.png 1233w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">2x speedup &#8211; add sum caching (technical optimization).</h2>



<p class="wp-block-paragraph"><a href="https://github.com/mgrzaslewicz/set-equalizer/commit/8b3f2f431b23a9d7fa29501f6f010fcac5dc13e6">git commit</a></p>



<p class="wp-block-paragraph">Replace</p>



<pre class="wp-block-code"><code>  private int sum(List&lt;Integer&gt; list) {
      int result = 0;
      for (var i : list) {
          result += i;
      }
      return result;
      return Math.abs(listA.sum() - listB.sum());
  }</code></pre>



<p class="wp-block-paragraph">With a sum caching <code>java.util.List</code> decorator</p>



<pre class="wp-block-code"><code>  public class SumCachingList implements SummingList {
      private final List&lt;Integer> decorated;

      public SumCachingList(List&lt;Integer> decorated) {
          this.decorated = decorated;
      }

      private int sum;
      private boolean sumCalculated = false;

      @Override
      public int sum() {
          if (!sumCalculated) {
              calculateSum();
              sumCalculated = true;
          }
          return sum;
      }

      private void calculateSum() {
          for (var i : decorated) {
              sum += i;
          }
      }

      @Override
      public boolean add(Integer integer) {
          sum = sum() + integer;
          return decorated.add(integer);
      }

      @Override
      public boolean remove(Object o) {
          var removed = decorated.remove(o);
          if (removed) {
              sum = sum() - (Integer) o;
          }
          return removed;
      }

     @Override
     public boolean removeIf(Predicate&lt;? super Integer> filter) {
         var anyRemoved = decorated.removeIf(filter);
         if (anyRemoved) {
             sumCalculated = false;
         }
         return anyRemoved;
     }

      // ...
  }</code></pre>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="659" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-1024x659.png" alt="" class="wp-image-85" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-1024x659.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-300x193.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0-768x494.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_flamegraph_2022-03-31_11-49_1703191218063_0.png 1235w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="185" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-1024x185.png" alt="" class="wp-image-86" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-1024x185.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-300x54.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-768x138.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0-1536x277.png 1536w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_2x_speedup_caching_methods_2022-03-31_11-49_1703191230949_0.png 1575w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">After above optimizations flamegraph looks like that</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="249" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-1024x249.png" alt="" class="wp-image-87" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-1024x249.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-300x73.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-768x187.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0-1536x374.png 1536w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_after_optimisations_2022-03-31_11-49_1703191360299_0.png 1577w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h1 class="wp-block-heading">Big picture optimisation VS technical optimisation</h1>



<p class="wp-block-paragraph">Ok, now you see that you can discover bottlenecks using async profiler recording. Above example is rather an easy one. For huge systems with tons of legacy code which is hard to maintain and change, it might be really hard.</p>



<p class="wp-block-paragraph">Even discovering slow parts of big systems is hard, but that&#8217;s a topic for another blog post.</p>



<p class="wp-block-paragraph">Technical optimizations are often easier to find.</p>



<p class="wp-block-paragraph"><strong>Technical optimization</strong></p>



<p class="wp-block-paragraph">You don&#8217;t necessarily have to understand what the application is doing. You can read straight from the flamegraph that adding a cache or optimizing loops is going to help.</p>



<p class="wp-block-paragraph"><strong>Big picture optimisation</strong></p>



<p class="wp-block-paragraph">You need to understand what your application is doing. You can optimize by organizing processing in a different way, e.g. discovering that you don&#8217;t have to fetch and process some data to in order to display particular piece of frontend.</p>



<h1 class="wp-block-heading">Run integration tests with profiler regularly</h1>



<p class="wp-block-paragraph">That should be part of your development process. Like running integration tests.</p>



<p class="wp-block-paragraph">In a perfect case, scenario of integration test should be as similar as possible to production scenario. This way you&#8217;d discover potential performance improvements or issues before production deployment.</p>



<h1 class="wp-block-heading">Know the difference between async profiler sampling modes: <code>Wall Clock</code> and <code>CPU</code> usage only</h1>



<h2 class="wp-block-heading">Wall Clock (Total Time)</h2>



<p class="wp-block-paragraph">If you&#8217;re interested in real latency of your system, including</p>



<p class="wp-block-paragraph">IO, e.g.</p>



<ul class="wp-block-list">
<li>connection polling</li>



<li>DB transaction locks</li>



<li>reads/writes</li>
</ul>



<p class="wp-block-paragraph">synchronization, e.g.</p>



<ul class="wp-block-list">
<li>waiting for critical section access</li>



<li>waiting for tasks in a thread pool</li>
</ul>



<p class="wp-block-paragraph">use <code>Wall Clock</code> sampling in async-profiler. This mode will collect events from threads in <code>SLEEPING</code> state also.</p>



<p class="wp-block-paragraph">Beware it affects performance of measured process more than measuring only <code>ACTIVE</code> threads. Why? Because measured JVM has more threads to iterate over every interval.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="576" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-1024x576.png" alt="" class="wp-image-89" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-1024x576.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-300x169.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-768x432.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0-800x450.png 800w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_Total_time_2023-12-21_16-11-35_1703186529232_0.png 1090w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">CPU only</h2>



<p class="wp-block-paragraph">In other words, without <code>Wall Clock</code> mode, measuring sampling only JVM threads in <code>ACTIVE</code> state</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="573" src="https://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-1024x573.png" alt="" class="wp-image-90" srcset="http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-1024x573.png 1024w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-300x168.png 300w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0-768x430.png 768w, http://deploymenteveryday.com/wp-content/uploads/2023/12/blog_post_bottleneck_analysis_JFR_CPU_Time_2022-03-31_11-49_1703186541469_0.png 1093w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">In the examples above you can see hot method <code>httpClient.newCall(request).execute()</code></h3>



<ul class="wp-block-list">
<li>Time spent measured with <code>CPU Time</code> is 120 ms</li>



<li>Time spent measured with <code>Wall Clock</code> (<code>Total Time</code>) is 9170 ms</li>
</ul>



<p class="wp-block-paragraph">Would you optimize CPU time in this case? Probably not. You&#8217;d first focus on IO as it&#8217;s the bottleneck.</p>



<p class="has-luminous-vivid-amber-background-color has-background wp-block-paragraph"><strong>Subject to optimize needs to be chosen case by case. In order to have this choice, you need have both <code>CPU Time</code> and <code>Total Time</code> in flamegraph &#8211; and it&#8217;s available only when using wall clock mode sampling</strong>.</p>



<h1 class="wp-block-heading">Resources</h1>



<ul class="wp-block-list">
<li><em><a href="https://plv.colorado.edu/papers/mytkowicz-pldi10.pdf" title="Evaluating the Accuracy of Java Profilers">Evaluating the Accuracy of Java Profilers</a></em></li>



<li>Safepoint bias problem
<ul class="wp-block-list">
<li><a href="https://jpbempel.github.io/2022/06/22/debug-non-safepoints.html">https://jpbempel.github.io/2022/06/22/debug-non-safepoints.html</a></li>



<li><a href="https://psy-lob-saw.blogspot.com/2015/12/safepoints.html">https://psy-lob-saw.blogspot.com/2015/12/safepoints.html</a></li>
</ul>
</li>



<li><a href="https://github.com/mgrzaslewicz/set-equalizer" target="_blank" rel="noopener" title="">https://github.com/mgrzaslewicz/set-equalizer</a></li>
</ul><p>The post <a href="http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/">Example of 40x speedup using async-profiler recording analysis (JFR file)</a> first appeared on <a href="http://deploymenteveryday.com">Deployment Every Day</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>http://deploymenteveryday.com/example-of-40x-speedup-using-async-profiler-recording-analysis-jfr-file/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
