<![CDATA[przemuh.dev [EN]]]>https://przemuh.devGatsbyJSFri, 01 Jan 2021 13:11:27 GMT<![CDATA[Short story about optimisation]]>https://przemuh.dev/en/blog/tree-performance-improvement-case-studyhttps://przemuh.dev/en/blog/tree-performance-improvement-case-studySun, 08 Nov 2020 00:00:00 GMT<p><em>This article has not been translated yet. Here is a Polish version. Stay tuned</em></p><p>Jakiś czas temu na <a href="https://twitter.com/przemuh/status/1319595759935852544" target="_blank" rel="nofollow noopener noreferrer">Twitterze zamieściłem zrzut ekranu</a> pokazujący flame-chart z narzędzia Profiler. Pracowałem wtedy nad poprawą wydajności aplikacji, którą rozwijamy w Egnyte. Pewna funkcjonalność, dla dużej ilości danych, zajmowała strasznie dużo czasu - 3,5 minuty! Przez ten czas w aplikacji pokazywany był &quot;kręciołek&quot;, a użytkownik nie wiedział, czy coś się dzieje, czy może coś się zawiesiło. Po kilku dniach pracy z Profilerem udało mi się zaimplementować poprawki, które zredukowały czas potrzebny do obliczeń z 3,5 minuty do 35 sekund. W tym wpisie chciałbym opisać jak do tego doszedłem.</p><h2>Opis funkcjonalności</h2><p>Zacznijmy od opisu funkcjonalności, która krótko mówiąc kulała pod względem wydajności. Jednym z głównych widoków w naszej apce jest tzw. &quot;Sensitive Content&quot;. Pokazujemy tu listę folderów z plikami, które zawierają wrażliwe dane. To mogą być numery kart kredytowych, dane medyczne, personalne i nie tylko. Dane te mogą podchodzić pod jedną z kilkunastu wbudowanych polityk np. HIPA, GDPR, ale również dajemy możliwość użytkownikom zdefiniowana własnej polityki np. w oparciu o utworzony wcześniej słownik. Początkowo widok &quot;Sensitive Content&quot; pokazywał tylko płaską listę folderów. W zeszłym roku nasz Product Owner wraz z zespołem UX doszli do wniosku, że praca z płaską listą folderów może być nieefektywna. Zamiast tego dużo lepszym, i w zasadzie bardziej naturalnym sposobem reprezentacji danych, będzie drzewko folderów.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:944px"> <a class="gatsby-resp-image-link" href="/static/f622f9b398ed8964cce4a32ffc9df3fb/966a0/sc-view.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:37.81779661016949%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABVElEQVQoz01R2U7EMBDr//8fLwiEuLTskftq0rTGk3YXIllJp45jz0yXm8ZVGdy0HZDz9aidQ8YpFqg0o7WOMjcEflufYGyAdRGGcCGN3biAyVqHmBJiTCQTIaKUGb2vqEtH6x25LThbD2Pc4NXaBjfngllQK7wPRMSktIY2li9aPM6EDwELBWVF7s907J0b/9uyjHrnYygVKWUopZFLwfR+0vg8G3xdLD5+NL4vvBRm2NTgckcsK66x4Ymcb3KEa2JFYN2wHnzBxSS8nxSuNmO6PRxSSBu6NIwTUVvD0neHiQ5f2V9HjrhvbMG2YSTYeh7RJZ20ao98ILGXYjvnPCI3isrKh6B30g5Dob/I21oHXymFeaagECx70w83/9d27OLwhYLBezhy772VBH3dhhER3B2q3Z0QJY5MeWOeO4ZDXnzjhJ3dB3Yfijjt64pEh/qI/AvbE2lTetVs3wAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Widok Sensitive Content List" title="Widok Sensitive Content List" src="/static/f622f9b398ed8964cce4a32ffc9df3fb/966a0/sc-view.png" srcSet="/static/f622f9b398ed8964cce4a32ffc9df3fb/3cf3e/sc-view.png 293w,/static/f622f9b398ed8964cce4a32ffc9df3fb/78a22/sc-view.png 585w,/static/f622f9b398ed8964cce4a32ffc9df3fb/966a0/sc-view.png 944w" sizes="(max-width: 944px) 100vw, 944px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Widok Sensitive Content List</figcaption> </figure></p><h2>Algorytm budowania drzewa</h2><p>Podejść do drzewek było już u nas w projekcie kilka. Głównie opierały się one na własności folderu jakim było unikalne <code>folderId</code>. Niestety, w przypadku &quot;Sensitive Content&quot; nie mogliśmy tego użyć, ponieważ nie wszystkie foldery mogły zawierać wrażliwe dane, a tylko dla takich folderów dostawaliśmy <code>folderId</code>.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">/Shared/A/B/ W tym wypadku mamy 3 foldery (Shared, A, B), z czego tylko dla B dostajemy folderId</code></pre></div><p>Takich lokacji z SC (Sensitive Content) może być multum. Problem wydajności pojawiał się już przy 250 tysiącach lokacji. A że nie jest to wyjątek utwierdził nas klient, u którego znaleźliśmy prawie <strong>milion</strong> folderów. To właśnie dla 1M elementów listy, czas budowania drzewka wynosił 3,5 minuty. Dlatego też, moje zadanie polegało na tym, że drzewko dla 1M elementów ma się budować w mniej niż 60s.</p><p>Wracając do algorytmu. Prosty - jak budowa cepa - tak by się wydawało :)</p><ol><li>Weź całą ścieżkę i podziel ją na fragmenty wg. separatora np. <code>/</code></li><li>Każdy folder wsadź do dwóch struktur: &quot;drzewiastej&quot; i &quot;płaskiej&quot;</li><li>Jeśli folder nie ma <code>folderId</code> traktuj go jako meta-folder</li></ol><p>Dla uproszczenia pomijam kwestię tego, że wspieramy różne źródła danych i te separatory mogą się mocno różnić :) Co więcej, jak się później okazało, niektóre źródła danych mogą mieć dwa foldery o tej samej nazwie, na tym samym poziomie zagnieżdżenia 😱. I jak je rozróżnić? Pominę też kwestię tego, że wynikowe drzewo mieliśmy przedstawić w formie &quot;sparse-tree&quot;. W skrócie - chodzi o to, że jeśli folder zawiera tylko jeden sub-folder, to ścieżka rodzica powinna być zwinięta/scalona.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Lista: /Shared/A/B/C /Shared/A/B/D --&gt; Drzewo: /Shared/A/B /C /D</code></pre></div><h2>Pierwsza, a w zasadzie druga implementacja</h2><p>Jak już wspomniałem wcześniej, nie było to pierwsze drzewo, jakie mieliśmy wyświetlić w aplikacji. W zupełnie innym widoku, też mieliśmy zrobić sparse-tree i nie chcieliśmy mieć kilku różnych implementacji. Dlatego napisaliśmy prosty moduł do budowania i zarządzania drzewkiem. Oparty został o dwa małe komponenty:</p><ul><li>funkcję <code>buildTree</code>, która przyjmowała płaską tablicę węzłów i miejsce (ścieżkę w drzewie) od którego miała te węzły wstawiać</li><li>&quot;plasterek&quot; (slice) z <code>redux-toolkit</code>, który zarządzał strukturą drzewa (rozwijanie, zwijanie węzłów itp.)</li></ul><p>Całe drzewo, a w zasadzie te dwie struktury &quot;drzewiasta&quot; i &quot;płaska&quot;, trzymane były w reduxie w następujący sposób:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> tree<span class="token operator">:</span> <span class="token punctuation">{</span> path<span class="token operator">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span> <span class="token comment">// root</span> children<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;Shared&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token comment">// path-part or folder name as a key</span> path<span class="token operator">:</span> <span class="token string">&quot;/Shared&quot;</span><span class="token punctuation">,</span> children<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;A&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> path<span class="token operator">:</span> <span class="token string">&quot;/Shared/A&quot;</span><span class="token punctuation">,</span> children<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> paths<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;/Shared&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> meta<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> ...nodeProps <span class="token punctuation">}</span> <span class="token property">&quot;/Shared/A&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> meta<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> folderId<span class="token operator">:</span> <span class="token string">&quot;some-unique-id&quot;</span><span class="token punctuation">,</span> ...nodeProps <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div><p>Taką strukturę otrzymujemy z pomocniczej funkcji <code>buildTree</code>.</p><p>Dzięki zastosowaniu <a href="https://redux-toolkit.js.org/" target="_blank" rel="nofollow noopener noreferrer">redux-toolkit</a>, a co za tym idzie biblioteki <a href="https://github.com/immerjs/immer" target="_blank" rel="nofollow noopener noreferrer">immer</a>, mogliśmy w bardzo prosty sposób wykonywać operacje na drzewie:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> initialTreeState <span class="token operator">=</span> <span class="token punctuation">{</span> initialized<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> tree<span class="token operator">:</span> <span class="token punctuation">{</span> path<span class="token operator">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span> children<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> paths<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">createTreeSlice</span> <span class="token operator">=</span> <span class="token parameter">treeName</span> <span class="token operator">=&gt;</span> <span class="token function">createSlice</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token operator">:</span> treeName<span class="token punctuation">,</span> initialState<span class="token operator">:</span> initialTreeState<span class="token punctuation">,</span> reducers<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token function-variable function">insertTree</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> tree<span class="token punctuation">,</span> paths <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> payload <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> paths <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>paths<span class="token punctuation">,</span> <span class="token operator">...</span>payload<span class="token punctuation">.</span>paths<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> node <span class="token operator">=</span> <span class="token function">getNodeByPath</span><span class="token punctuation">(</span>payload<span class="token punctuation">.</span>parentPath <span class="token operator">||</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span> tree<span class="token punctuation">)</span> node<span class="token punctuation">.</span>children <span class="token operator">=</span> payload<span class="token punctuation">.</span>tree<span class="token punctuation">.</span>children <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">toggleNode</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> tree <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> payload<span class="token operator">:</span> path <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> node <span class="token operator">=</span> <span class="token function">getNodeByPath</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> tree<span class="token punctuation">)</span> node<span class="token punctuation">.</span>expanded <span class="token operator">=</span> <span class="token operator">!</span>node<span class="token punctuation">.</span>expanded <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>Pomocnicza funkcja <code>getNodeByPath</code> służy do wyszukiwania węzła po ścieżce. Potrafi też wyszukać węzeł w sparse-tree.</p><h2>Pierwsze podejścia do optymalizacji i pierwsze błędy</h2><p>No i wszystko pięknie-ładnie, ale przyszedł klient, 1 milion folderów i jebs... Product Owner zakłada Epic w Jirze pt. &quot;Support 1M folders on SC tree view&quot;. Szybka burza mózgów i od razu kosz pełen pomysłów:</p><ul><li>a może by tak budować drzewo w locie, jak parsujemy JSONa?</li><li>a może by tak budować drzewo w web-workerze, przynajmniej nie zablokujemy głównego wątku na 3,5 minuty?</li><li>a może by tak, pizgnąć to wszystko i wyjechać w Bieszczady? ⛰ 🐑</li></ul><p><img src="https://media.giphy.com/media/kPtv3UIPrv36cjxqLs/giphy.gif" alt="A może by tak..."/></p><p>Pierwszy błąd - nikt nawet nie odpalił Profilera, żeby zobaczyć co zajmuje tyle czasu. Każdy założył, że obecna implementacja drzewka wymiata i lepiej być nie może. Sam Profiler na pierwszy rzut oka nie jest prostym narzędziem i może to było powodem tego, że rzuciliśmy się wtedy na tego typu pomysły jak budowanie drzewa &quot;w locie&quot; czy przeniesienie tego do web-workera. Warto też podkreślić, że sam Epic w Jirze powstał już jakiś czas temu, a do samej implementacji usiadłem w połowie października.</p><p>Pewnie zastanawiacie się - ale jak to w locie? Przecież jak idzie request to dopiero jak przyjdzie odpowiedź to przeglądarka parsuje JSONa i daje odpowiedź. Tak, ale...w tym przypadku nasi backendowcy też musieli podziałać trochę w kwestii optymalizacji i zamiast zwracać pełne dane to zaczęli tę naszą listę SC zwracać na zasadzie stream&#x27;u. Dzięki temu mogliśmy np. wykorzystać bibliotekę <code>oboe.js</code> to parsowania JSONa w locie.</p><p>Oczywiście spróbowałem tego podejścia, no bo w końcu ktoś to wpisał do zadania w Jirze, trzeba było sprawdzić co nie? 😜 Fajnie, JSON parsował się &quot;w locie&quot;, ale stream zamiast 10s trwał 30s, a jeszcze nie zacząłem nawet budować drzewa. Dlatego odpuściłem i postanowiłem poszukać gdzie indziej.</p><h2>Web-worker</h2><p>Podejście z web-workerem też przetestowałem. Ale tu napotkałem zupełnie inny problem. Ok - mogę sobie pobrać 1M elementów i zbudować na tej podstawie drzewo, ale muszę je później przesłać z wątku web-workera do wątku głównego. Struktura drzewa jest dość obszerna, razem z danymi, które zapisywaliśmy w tej płaskiej strukturze <code>paths</code>. Jeśli chcemy przesłać tak duże dane z jednego wątku do drugiego, przeglądarka musi te dane zserializować, przesłać, a następnie ponownie sparsować. To też powodowało &quot;zamrożenie&quot; przeglądarki na czas przesyłania z jednego miejsca pamięci do drugiego. Oczywiście są sposoby na przesłanie &quot;bezpośrednie&quot; (bez kopiowania) poprzez tzw. Transferable Objects np. ArrayBuffer, ale uznałem, że na chwilę obecną to może być gra nie warta świeczki i postanowiłem sprawdzić, czy faktycznie ta nasza implementacja drzewka była tak zajebista jak myśleliśmy 😜</p><h2>Profiler podejście pierwsze</h2><p>Usiadłem przed ekranem komputera, odpaliłem dev-toolsy i wcisnąłem przycisk &quot;record&quot; w Profilerze. Po chwili dostałem taki kolorowy wykresik, który przypomniał mi czasy defregmentatora dysków z Windowsa 98 🤣</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/08d444b93bebf165ed87c320556ad7b0/d9ed5/flame-graph-violet.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:62.5%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACOklEQVQ4y42TeW/aQBDF/ZGrtiSUw6Fqv1YVVYqapkkgKeEU4JgjGBx87YkPXscODUhVpP7x05ud3X07s14blVoNpZMyTkunqFNcr5+h8qmKWtWEaZoUn1BcRqV8gtL7d4V+bpiom1V8+dpA46yGRr0Ks1ZB6cNHGH7oYRMQvgsmQ3AtIWIFGWtwFUGLGRK5QKZWhINMOkjEEimR8KeXmHIsWMB1HRhaawjBCQHORaFZtiMAEiT4f6TSMISkipR6hdPY91YIwhW8pQ1/0odvDY7owytyR9A4sIeIgmcy1Cm1loEVpGS6g+V00N58w8PwHL2L75i2bmC3rmE3fxHXmDZvMGs1C83npve3sK+usB6Nc8MMx8jtDjxMEW1iOP0N2pcPGA8tPPanGC8eMVpN0Jn30G33MLq3MGiO0G8OYd8uEFn6X8MCqlbSBfJRDP/So5MdrAdLMKapiy08yWB3uljcD2HddTG9HsJtrhAtGQz5hqFIdoieNKJbutt2DP5bQ/Jd0YGKgfWFB+88gPfTQ/SDgbVo3Uq9YZgTU4XPCuGYI5qRzvIPlu7nUoQ9ynUkVaURWqQTCeVu32g5p7jLLdgsAnMUGJ0uXg2JeQLxRJXzhA6mTuYcMoipwu3hY+QcDAmZgW+24B498g291/wV/F0TUByRKRkKMmKugqKxkS/KnwuTdJJKD9CmosWQNjAah8khn2ueEy8IRnNkKsncWLvP8IMQYcgKGP0tjMu9EmxPJA654zw/mif9A0zFwtIb7Iv6AAAAAElFTkSuQmCC&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Flame graph" title="Flame graph" src="/static/08d444b93bebf165ed87c320556ad7b0/105d8/flame-graph-violet.png" srcSet="/static/08d444b93bebf165ed87c320556ad7b0/3cf3e/flame-graph-violet.png 293w,/static/08d444b93bebf165ed87c320556ad7b0/78a22/flame-graph-violet.png 585w,/static/08d444b93bebf165ed87c320556ad7b0/105d8/flame-graph-violet.png 1170w,/static/08d444b93bebf165ed87c320556ad7b0/28884/flame-graph-violet.png 1755w,/static/08d444b93bebf165ed87c320556ad7b0/92bee/flame-graph-violet.png 2340w,/static/08d444b93bebf165ed87c320556ad7b0/d9ed5/flame-graph-violet.png 2880w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Flame graph</figcaption> </figure></p><p>Pierwsze co rzuciło mi się w oczy to ten fioletowy kolor, który zanurkował bardzo, bardzo głęboko. Po bliższym spojrzeniu okazało się, że bardzo dużo tych fioletowych elementów to sprawka <code>immer.js</code>. Szybki rzut oka w dokumentację i boom! strzał w dziesiątkę. Okazuje się, że przy &quot;wkładaniu&quot; dużej ilości danych poprzez <code>immer</code> możemy przyspieszyć ten proces poprzez <code>Object.freeze</code> <a href="https://immerjs.github.io/immer/docs/performance#performance-tips" target="_blank" rel="nofollow noopener noreferrer">tutaj więcej info</a>. Zabieg ten pozwolił mi na zejście z 12,54s na 11,24s dla 54K elementów. Dla 1M skok był oczywiście proporcjonalnie większy. No ale to nadal nie było to...</p><h2>Od profilera, do źródeł</h2><p>Wiedzieliście, że jak klikniecie na dany blok w Profilerze, a następnie przeniesiecie się do pliku, to dostaniecie czasy dla poszczególnych bloków kodu? Nie!? 😎 To teraz już wiecie ;)</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/1ba2423b674f0cab158baa993f4c7cfd/0d0e4/source-before.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:90.73170731707317%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAACUElEQVQ4y4VUa5eaMBT0//+39kPrqa26qy4LKpDwSMgbpje40kWtyzEnYJLJ3Llz76IVEnnJwOsGQjokSYbt5oDdrsLbW4PDniHLzhBCw1mLEAL6Pozz59H3Pbz3WKiug2pPMLJEfAJtbniH/a7BdluMF7CyhlIe3nkMw4D/PRF0IaWEqFICPeO6d9CeQC04N7DWwxqLrnOQ0tDcEaN+BP48JkClFDr+AtMmuN7t6PCv5RHr9ZHY8ZHZeNENwO08AlrS5fqMCx+LjRBYbzZYrTLSMicN238AGGahzwCN0RDnn+jYetqgRYdjXiIrK7SyRtM044gHHjGbAWqtwZJvYOmPWVh1XaEoKuz3pLGwDwEeMlRKozkt0RZXhgN62pBmDWW6wOnIiZ2C1u4S7ICZjneAMWuyyaElnwDjjzMxejDPFYwOs8MDnoRsjCHDasqkmRYMvdddBWko3M7AeLJPuIxn2Z5sI8s/ZJ3dxMJqg/2hwPJ3gn1yQHpieKHvnNfwwd/pNwMUZA+tFZxzc23CgNMrozIssXvleE84+bKhyqnHJFkbHjOMgN65O00CMbFUbm3r0IpYKZYyb+j7UjVPASWFq9tsCsO7AEtAog3ExoymjsBKhbv6vQs5ZrlKv0MWq2kTq6g5pGTs/EQGrynTsYYVot7Rt0+TErMcG4ORbGoOhiQoOEdZMZRMwhjqND7qdhm3zO4qxRlB1ukuf9KiI6vwQiF9F6h59CKjqiGtffi6UiJDbxUCeS82zlgRmkTvKcvOXZqm9z0x6z9qeXga8l+P/3vSoyCcKQAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Czasy przed optymalizacją dla buildTree" title="Czasy przed optymalizacją dla buildTree" src="/static/1ba2423b674f0cab158baa993f4c7cfd/105d8/source-before.png" srcSet="/static/1ba2423b674f0cab158baa993f4c7cfd/3cf3e/source-before.png 293w,/static/1ba2423b674f0cab158baa993f4c7cfd/78a22/source-before.png 585w,/static/1ba2423b674f0cab158baa993f4c7cfd/105d8/source-before.png 1170w,/static/1ba2423b674f0cab158baa993f4c7cfd/0d0e4/source-before.png 1230w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Czasy przed optymalizacją dla buildTree</figcaption> </figure></p><p>To co się rzuca w oczy to 229ms dla zbudowania prostego ciągu znaków 🤯 jakim jest aktualna ścieżka. Okazało się, że to zwykłe niedopatrzenie można było zastąpić krótszym kawałkiem kodu, który koniec końców zajmuje 1.7ms.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/831c9d5d2fd63e39d71b3e58d9daf5e6/7be33/source-after.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:100.38510911424905%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAACtElEQVQ4y41UCXKjMBDM/3+XrdojTuIb29ygCx1AbyMn2HFc3hWeMqURrZ6ZnnnSWsO0B1h5hDYeSZJi8bbG66rELlFIU4X1ukR6KuCsRfAeIQT0fX/XnrrOwtQbiGKJcQSc86gKgdWyxvNzjsVii90uRVlpdMZhHHo8WgQ06HQTbQKMqx/gtEfdOGjdEcjC8yJjRkZh4L2Lx0Z+cGtPrdT8uESnigg4YY6uR5tbbLcSmn4hFNqmRVFUmFJU14zKhBn0C0OhOgSnyEDPzuAcDmmOZZLhVOaRVSs6hq2glCPDAT709wGnkEWxQnV6PR+YnmGEIZP9IcPLosHxKBmBh0wNLFPxuW7BPgA7aBalyd6/HFLM3W6bsCAK253k/hAvus3ZdS4joGN4482NPWXRNBZt+7iid0M2ZmK4onTWc8jTLy8r/HxfY01dJnmJQ3HE+pjwP0clKxjPVFnxnaHWBrp8h66WFycfpRWrKVgIi4omBN9LhaqkHqnda3Y3gDwgc/S2mTedt8iqCvuiQNHmaFSDjLJpuhqs7wcIvuXxHDKL0qa/UCY/Lg6abh3eXjL8+bPBZpNhuSxYHAJnilq0AMa7eYwMvVUsRDeDffp1aZBS3JudZi/XrHpLYEFgEYs2ibtnV31hqJRi/9qzLK5CmAaA8z3zFWCtp5gD33u+c5+dZMzZNww3gHHaNAk6ccCllQcETQAZIFoKWkpKSMfedi48lE7Moch+M7yXedN0HqeixiGrkbI4U5Xr2pGRj5FMYU52zW5mOElAlwvI/ALoOF0K9vJ6V2GbNMxvT5Zky36WUpCtiwMiBP8d0HJoOttF+9wcOCjrusN+ryGFY96mEWbIKvxPp5gI1gd/aT3myZDF6WQ4sjpOGDtP6X/28sQwHvxwTNVU0sZBEMIQbRjGhxPmev0FrkwWn/TxJDQAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Czasy po optymalizacji dla buildTree" title="Czasy po optymalizacji dla buildTree" src="/static/831c9d5d2fd63e39d71b3e58d9daf5e6/105d8/source-after.png" srcSet="/static/831c9d5d2fd63e39d71b3e58d9daf5e6/3cf3e/source-after.png 293w,/static/831c9d5d2fd63e39d71b3e58d9daf5e6/78a22/source-after.png 585w,/static/831c9d5d2fd63e39d71b3e58d9daf5e6/105d8/source-after.png 1170w,/static/831c9d5d2fd63e39d71b3e58d9daf5e6/7be33/source-after.png 1558w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Czasy po optymalizacji dla buildTree</figcaption> </figure></p><p>Pomyślicie - (ironicznie) wow... 227ms... &quot;brawo Ty&quot; 👏. Czym jest 227ms? Jeśli spojrzymy na to jako pojedynczą wartość - to fakt...mikro-optymalizacja. Ale pamiętajcie, że celem było obsłużenie 1M elementów, a operacja sklejania ścieżki dotyczyła każdego sub-folderu.</p><h2>Mój spread operator AKA Object.assign taki piękny</h2><p>Jak zrobić płytką kopię obiektu, bądź rozszerzyć inny obiekt - nic prostszego - spread operator <code>...</code>. Jeśli musicie wspierać przeglądarki takie jak IE11, to pewnie korzystacie z babel.js - tak jak my...no i taki spread operator, koniec końców jest tłumaczony na <code>Object.assign</code> (w wielkim uproszczeniu).</p><p><code>Object.assign</code> jest <a href="https://twitter.com/dan_abramov/status/980436488860196864" target="_blank" rel="nofollow noopener noreferrer">stosunkowo wolny</a> i przy większej skali może sprawiać problem. W tym przypadku postawiłem na zwykłe kopiowanie per klucz. Dzięki temu prostemu zabiegowi zbiłem 154ms do 44ms. I znowu, dla pojedynczych elementów to nie ma absolutnie żadnego znaczenia, ale kiedy iterujemy po dużym zbiorze danych takie optymalizacje mogą zdziałać cuda.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/7bed4f76daac0b292144d25c8a68996a/35252/dan.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:93.85382059800665%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAACuklEQVQ4y51U23LaQAz1//9EX/sTfehDp5k2MwkJ2MQ2EDBgx1eM7xc41dnYCcn0kunOyNrVylrp6OxqztLC9MdX7F0Pi+UKy9UKZVXhfD6jP51w+qDQv2k7aPrNNb58/oQkKxBGMYIwRtf3+J/BwFpelNh7T1ivN9hsHGy3O+x2e9FbZXOcLWzbhuu6UoUrPuLnOFg9Pirf9XoNPwhUQGapeZ6HyWQCXdcxm82UXi6XsCwLhmFgOp2KnuPu7h6mKbb5XPx03Iv99naCB9NE0zTPGTKg++Tj+maCqW7AeLCgmzbm1gKTqRzAtfEASzLkj7a9gCkH2YuFCsr1TBKwLBur1SOOxwzaNm3wc5djHpSY+hWMoIIZVrBEzKiGn7c4CaYj+P0wH3XXdUrGtVa2PZL6jLwDDlWPtutl8zTI+3n/zt4rJnB+Op2V1jg5RD6i4AlVWaJt25dT/yVt+zov5d+6bqARzOvvV7j6doVDekSapiiKAnleoKrqD1MmyzJUdQ2NnCvLSoKlCtRIuBgnCZLkoORwoKRIqOXA+FghFc7SHsWx0v3A20aq04gBjfu9C0/4mOe54NgpKlAIAWWcN4IxS73cJ//ko+ZaO3RoHGEYIhiIykEnHkSMuHeUShKpgBVdjvMYkCVXcnd931c/UFgym0WnWrIIo0gBH4mmL/dZyRjoTYYKQwHf9Xx4QvIgiGTdCMCtEnU1XYGiqGQ/ECyPyieKkz8EFEzYbt5HPwjVA0FnNoANyrJc2ZlhEEZqzQypLwM+vzZsipCRFNk4O3kI5LLLA7GTBq03W8FKKFTKbfFDybZRL1GWC5ZRglQY8X6wWRo/zLBu2mc9zJtB3tjry3nz4vPiS2Kzy4XwkDgym1HTlg/2egz8N5HD1APLUpI0QyE/UmdS/pHElZJiaUA53JZLrH4rg88vUpezjjrk5acAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Dan Abramov o Object.assign" title="Dan Abramov o Object.assign" src="/static/7bed4f76daac0b292144d25c8a68996a/105d8/dan.png" srcSet="/static/7bed4f76daac0b292144d25c8a68996a/3cf3e/dan.png 293w,/static/7bed4f76daac0b292144d25c8a68996a/78a22/dan.png 585w,/static/7bed4f76daac0b292144d25c8a68996a/105d8/dan.png 1170w,/static/7bed4f76daac0b292144d25c8a68996a/35252/dan.png 1204w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Dan Abramov o Object.assign</figcaption> </figure></p><h2>Agregacja wartości</h2><p>Po &quot;podkręceniu&quot; immera i wyrzuceniu kliku Object.assign, bądź przepisaniu ich na prostą pętlę, skończyły mi się pomysły na &quot;proste&quot; optymalizacje. Trzeba było podkręcić sam sposób budowania drzewa.</p><p>Poprzednia implementacja, dzieliła sobie SC lokacje wg. źródła i dla każdego z nich budowała pod-drzewo. Dla każdego pod-drzewa liczone były zagregowane wartości (np. jeśli folder sam w sobie zawierał 10 SC, ale miał dodatkowo 50 sub-folderów, chcieliśmy pokazać zsumowane wartości). Dla każdego takiego pod-drzewa, wykonywane były sumowania, a następnie węzeł źródła był aktualizowany wg. zsumowanych wartości dla wszystkich folderów.</p><p>Każda taka operacja wrzucała coś do stanu w redux&#x27;ie. Pomyślałem - a na co to komu? A komu to potrzebne? Przecież nie pokazujemy drzewa dopóki wszystko nie jest policzone i zaktualizowane. Dlatego, zmieniłem kod tak, aby najpierw zbudować w pamięci całe drzewo wraz z policzonymi zagregowanymi wartościami, a następnie za pomocą jednej operacji, wsadzić zbudowane drzewo do reduxa.</p><p>Co więcej - w wymaganiach drzewka, było napisane, że pewne węzły miały być domyślnie rozwinięte np. pierwszy poziom + potencjalnie wcześniej zaznaczony element na liście (z listy do drzewka można przejść za pomocą prostego przycisku). Wcześniej operacje rozwijania były wyzwalane za pomocą akcji <code>toggleNode</code>. To też zmieniłem - zamiast odpalać reduxową akcję, po prostu zmieniam wartość <code>expanded</code> na <code>true</code> bezpośrednio w obiekcie węzła.</p><p>Można zapytać - co Ci to dało drogi Panie?</p><p>Dla 54K elementów zjechałem z czasu 12,25s na 2,4s 🚀</p><p>Łogień w szopie :) Product Owner cały w skowronkach.</p><p><img src="https://media.giphy.com/media/ciwIz38tlvDFH08Yuu/giphy.gif" alt="Wow"/></p><h2>Testy dla 1M</h2><p>Poprosiłem backendowców, żeby przygotowali mi środowisko do testów dla 1M elementów. Chciałem sprawdzić czy moje optymalizacje dla 54K znajdą uzasadnienie. No i uśmiech nie uciekł z mej twarzy :)</p><p>Przed optymalizacją czas budowania drzewa wynosił ~3,5 minuty. Po zaaplikowaniu wyżej wymienionych zmian udało się zjechać do 59s.</p><p>Mniej więcej ~70% oszczędności. W sumie można by powiedzieć - job done - miało się budować w mniej niż 60s... 59 to mniej niż 60 😅 Jest git...</p><p>Trochę byłem już zmęczony tym grzebaniem w de-facto w nie swoim kodzie...ale kumpel z zespołu słusznie stwierdził:</p><blockquote><p>No fajnie, fajnie, ale dla mnie nadal to jest wolno.</p></blockquote><p>Dodał też potem, że nic mi nie ujmuje i wg. niego wykonałem kawał dobrej roboty...ale trudno było się z nim nie zgodzić. Od momentu kliknięcia w element nawigacji, do czasu wyświetlenia widoku, użytkownik musiał poczekać łącznie 90s:</p><ul><li>25s pobieranie danych (streaming)</li><li>6s przeglądarka parsuje JSONa</li><li>59s budowanie drzewa</li></ul><p>Jako użytkownik, gdybym przez 90s widział tylko spinner (&quot;kręciołek&quot;) to by mnie szlag trafił :) Nie chcę myśleć, co czuli nasi użytkownicy jak musieli czekać 3,5 minuty...pewnie żaden nie wytrzymał 😅</p><p>...no...ale wracając...kumpel mówi: &quot;wolno&quot;...no to ja od razu: &quot;co!? wolno!? ja Ci pokażę!&quot; 🤣</p><h2>Generowanie ID</h2><p>Znowu zanurkowałem w Profiler. Dla meta-folderów generowny był <code>folderId</code>. Było to spowodowane tym, że inne miejsce w kodzie tego <code>id</code> potrzebowało (mniejsza o to). Koniec końców to generowane id nic nie znaczyło (nigdy nie było wysyłane do backendu). Ktoś jednak wymyślił, że to meta-folder-id ma być haszem ze ścieżki...</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">createUniqueIdForLocation</span> <span class="token operator">=</span> <span class="token parameter">path</span> <span class="token operator">=&gt;</span> <span class="token function">btoa</span><span class="token punctuation">(</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>Funkcja <code>btoa</code> koduje ciąg znaków jako base64. Sama w sobie trwa średnio 0,25ms...czyli ułamek milisekund. Ale gdy mocniej się zastanowimy - a na co komu ten hasz? a na co komu to potrzebne?</p><p><img src="https://media.giphy.com/media/s239QJIh56sRW/giphy.gif" alt="Ale po co?"/></p><p>No właśnie! Jeśli meta-folder-id to tylko base64 ze ścieżki, która de facto zawierała też w sobie <code>id</code> źródła, więc była unikatowa względem całej listy, to po co to w ogóle ten cały hash?</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">- id: createUniqueIdForLocation(path), + id: path, name: getLocationName(path),</code></pre></div><p>Ten jeden diff sprawił, że dla 1M elementów zszedłem z 59s na 35s, co dało ~40% zysku 🤯</p><p>Czyli teraz klient nie czekał już 90s a 66s - łącznie z pobieraniem i parsowaniem danych! Biorąc pod uwagę, że wymagania mówiły o budowaniu drzewka w czasie mniejszym niż 60s, to chyba Product Owner oraz klienci powinni być zadowoleni 😅</p><h2>Dalsze kroki</h2><p>Oczywiście nie spoczywamy na laurach. Blokowanie użytkownika na 60s nadal jest kiepskim pomysłem, dlatego dalej myślimy o ulepszeniu implementacji. Być może wyrzucimy to w końcu do web-workera. Kto wie? Może uda mi się dzięki temu zebrać materiał na następnego posta 😉.</p><h2>Podsumowanie</h2><p>Lekcja pierwsza - zamiast gdybać i sypać pomysłami z kapelusza o web-workerach, lepiej odpalić Profiler.</p><p>Lekcja druga - jeśli operujesz w dużej skali, iterujesz po dużym zbiorze danych, to optymalizacje na poziomie <code>ms</code> dla jednej iteracji potrafią czynić cuda 🚀</p><p>Lekcja trzecia - do reduxa wrzucaj dopiero wtedy, kiedy jesteś gotów 💪</p><p>Lekcja czwarta - jeśli nie ma potrzeby, to nie komplikuj sytuacji 😉 (patrz ID &amp; btoa).</p><p>Mam nadzieję, że dzięki tej historii sięgniecie wcześniej do Profilera i uda się Wam poprawić wydajność nie jednej aplikacji.</p><![CDATA[10 golden advices for junior developer]]>https://przemuh.dev/en/blog/10-golden-advices-for-junior-developerhttps://przemuh.dev/en/blog/10-golden-advices-for-junior-developerTue, 22 Sep 2020 00:00:00 GMT<p><em>This article has not been translated yet. Here is a Polish version. Stay tuned</em></p><p>Czy ten tytuł to clickbait? Oczywiście, że tak. Nie ma na świecie dwóch takich samych osób. To co sprawdziło się u mnie niekoniecznie musi się sprawdzić u Ciebie. Tak prawdę mówiąc, to tytuł tego posta powinien brzmieć &quot;Co chciałbym przekazać <strong>sobie</strong>, gdybym dzisiaj miał zaczynać jako junior developer&quot;. Ale uznałem, że to trochę zbyt długie. Kto wie, może niektóre z poniższych wskazówek przydadzą się Tobie. Czy niektóre z tych rad są kontrowesyjne? Pewnie tak. Ale wszystkie z nich są szczere, i właśnie takie chciałbym usłyszeć gdybym wchodził jeszcze raz w świat IT. Traktruj ten wpis, raczej jako taki list Przemka do Przemka :)</p><p><strong>TL;RD</strong></p><ol><li>nie buduj portfolio</li><li>załóż konto na Twitterze</li><li>uważaj na celebrytów-IT</li><li>nie kupuj książek</li><li>eksperymentuj, baw się kodem</li><li>naucz się świadomie zarządzać czasem</li><li>zainwestuj w umiejętności miękkie</li><li>znajdź mentora</li><li>kup notatnik</li><li>doszlifuj anglielski</li></ol><h2>Nie buduj portfolio</h2><p>Kiedy zaczynałem szukać swojej pierwszej pracy jako programista (2012 r.) nie wiedziałem, że wyląduję na frontendzie. Nie miałem konta na githubie. Nie miałem portfolio. I gdybym dzisiaj miał szukać pracy, to też bym takiego portfolio nie budował. A już na pewno nie wrzucałbym tam aplikacji typu ToDo List, albo Weather App. Nie zrozum mnie źle, pisanie takich aplikacji jest jak najbardziej ok. Dzięki temu uczysz się jak składać działającą całość. Ale wrzucanie tego do portfolio jak dla mnie nie ma sensu.</p><p>No dobra...ale co zamiast portfolio?</p><p>Odpowiedź jest prosta - Open Source. Na githubie jest mnóstwo bibliotek, do których możesz kontrybuować. Wybierz sobie swoją, przeczytaj dokumentację, spróbuj zbudować lokalnie, zobacz jak wygląda lista Issues - z czym ludzie mają problemy. Dzięki temu nauczysz się o wiele więcej niż przy ToDo czy Weather App. Czytanie cudzego kodu to jedna z najważniejszych umiejętności w byciu programistą. Mówiąc o kontrybucji do Open Source, nie mam na myśli np. nowych ficzerów Reacta (chociaż do odważnych świat należy). Czasami jedna linijka kodu potrafi rozwiązać czyjś problem. Ba! Nie musisz wcale kodować. Pisanie dokumentacji to też cegiełka do Open Source. Pierwszy commit Kent C Dodds&#x27;a w ramach Open Source to była literówka. Nie wierzysz? - <a href="https://kentcdodds.com/blog/how-getting-into-open-source-has-been-awesome-for-me" target="_blank" rel="nofollow noopener noreferrer">zobacz ten wpis</a>.</p><p>Podsumowując - jedno Twoje zdanie podczas rekrutacji - &quot;aktywnie udzielam się w świecie Open Source&quot; znaczy więcej niż portfolio z fajerwerkami.</p><h2>Załóż konto na Twitterze, ale ...</h2><p>Znowu - kiedy zaczynałem szukać pierwszej pracy, jedynym portalem typu social media był dla mnie Facebook. Dopiero po czasie zobaczyłem ile mnie omija. Jeśli chcesz być na bieżąco z informacjami dot. technologii to Twitter jest chyba najlepszą opcją. Facebook = rodzina i przyjaciele, sprawy for fun, śmieszki, heheszki, memy, zdjęcia. Twitter = nowinki technologiczne, ciekawi ludzie z branży, motywacja, inspiracje. Dlatego, jeśli jeszcze nie masz konta na Twitterze, to czym prędzej je zakładaj. Poszukaj kilku osób z branży, a potem poszerzaj listę obserwowanych. Nie bój się również usuwać ludzi z listy. To nie są Twoi przyjaciele - nikt się nie obrazi :) Jeśli nie podobają Ci się treści, jakie wrzuca obserwowana przez Ciebie osoba - po prostu przestań ją obserwować, albo wycisz jej tweety.</p><p>Z durgiej strony - nie obrażaj się, jak Twój &quot;idol&quot; wrzuci od czasu do czasu posta niekoniecznie związanego z programowaniem. Koniec końców jest to portal typu social media. Zrzut ekranu ze Spotify jeszcze nikomu krzywdy nie zrobił ;)</p><p>No ale, co z tym &quot;ale&quot;?</p><h2>Uważaj na celebrytów-IT</h2><p><em>pfff</em> &quot;że co!?&quot;. Wierz mi, albo nie, &quot;kiedyś to było&quot;. Może inaczej - kiedyś to NIE - było tylu blogów, tylu informacji, tylu kursów, tylu &quot;mentorów&quot;. Z czasem ludzie podchwycili temat pt. &quot;praca w IT = dużo hajsu&quot;. Zaczął się napływ ludzi do branży, bo to przecież &quot;łatwe pieniądze&quot;. Siedzi się tylko i klepie w klawisze. Jak grzyby po deszczu zaczęły wyskakiwać kolejne szkoły programowania, bootcampy, kursy, mentorzy. Zrobił się z tego niezły biznes. Przecież każdy chce zarobić - co nie? - co w tym złego?</p><p>Nic. Jeśli masz pieniądze to je wydajesz jak chcesz. Gdybym tylko miał sobie dawać w tej kwesti radę, to powiedziałbym &quot;uważaj na celebrytów w IT&quot;. Trochę takich ludzi szufladkuję, sorry. Naczytali się Aniserowicza i Szafrańskiego, jak to można zarabiać na blogu, kursach itp. Sami ledwo co skończyli bootcamp, liznęli pierwszej pracy a już wydają swój autorski kurs - &quot;Programowanie w HTML dla zaawansowanych&quot;. Brzmi jak dowcip? Niestety. Brzmi jak zazdrość - kto wie - być może. Niestety nikt nie uczy tego, jak odfiltrować dobry content od tego skopiowanego i nastawionego na szybki zysk. Czasami nie jesteśmy w stanie zweryfikować doświadczenia danej osoby. Kto wie - może naczytał się pierdół o żabach i teraz na siłę próbuje zainteresować tym innych :) Sam musisz sobie wyrobić czujnik na takie osoby - Przemku.</p><p>Dobra wiadomość jest taka, że tych dobrych &quot;dusz&quot; jest więcej. A takie celebryto-IT-pijawki zdarzają się sporadycznie.</p><p>PS. Nie zrozum mnie źle. Dzielenie się wiedzą na blogu, vlogu (whatever) nawet jeśli dopiero co nauczyłeś się &quot;czegoś&quot; jest SUPER! Ale natychmiastowa próba zarabiania na tym - już nie - przynajmniej nie dla mnie. Gdy płacę za kurs, to chcę mieć pewność, że dana osoba &quot;zęby na tym zjadła&quot;, a nie naczytała się pierdół o żabach ;)</p><h2>Nie kupuj książek</h2><p>A przynajmniej tych o technologiach. A już na pewno nie kupuj ich z myślą &quot;kiedyś przeczytam&quot;. Książki o technologiach szybko się starzeją. Zwłaszcza te o technologiach frontendowych, o frameworkach itp. Jeśli chcesz mieć fajną podstawkę pod monitor - spoko, your choice :) Mam takie dwie cegły pt. JAVA ^^ #naPóźniej.</p><p>Zamiast tego, skup się na książkach ponadczasowych. &quot;Clean Code&quot;, &quot;Clean Coder&quot;, &quot;Pragmatyczny Programista&quot;, &quot;Zawód Programista&quot;, &quot;Refaktoryzacja&quot; ... to są książki, które się nie starzeją. Po takie książki warto sięgać kilkukrotnie w swojej karierze. Za każdym razem wyciągniesz z niej coś innego, będziesz miał inny punkt widzenia, inny poziom doświadczenia.</p><p>I żeby było jasne - nie ma nic złego w książkach o samych technologiach/frameworkach - o ile kupisz aktualną wersję i przeczytasz ją zaraz po zakupie. W innym przypadku - &quot;daj se siana&quot; ;)</p><p>PS. Jeśli możesz - czytaj w oryginale, inaczej mówiąc - uważaj na tłumaczenia. Wiem, że oryginały są znacznie droższe w porównaniu z wydanymi nad Wisłą &quot;tłumaczeniami&quot; - ale warto. Obserwuj takie strony jak <a href="https://www.humblebundle.com/" target="_blank" rel="nofollow noopener noreferrer">HumbleBundle</a>, tam często pojawiają się &quot;paczki&quot; książek np. z wydawnictwa <a href="https://www.oreilly.com/" target="_blank" rel="nofollow noopener noreferrer">O&#x27;Reilly Media</a> i można je dostać za &quot;śmieszne&quot; pieniądze.</p><h2>Eksperymentuj, baw się kodem</h2><p>Często na grupach dla początkujących czytam: &quot;najpierw skup się na podstawach, dopiero potem zajmij się Reaktem&quot;. Ding-dong - Bullshit detector - Ding Dong. Owszem - podstawy pt. zmienne, pętle, funkcje wypadałoby ogarnąć przed frameworkiem. Ale jak już łykniesz podstawowej składni to śmiało wypływaj na głębie. Podczas nauki samego Reakta otrzesz się o funkcje wyższego rzędu, kompozycje, destrukturyzację i to w takiej praktycznej formie. Nie ma sensu czekać!</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">It&#x27;s fine to learn React while you&#x27;re learning JavaScript. Frameworks can teach you closures, higher order functions, ternaries, etc...</p>— Eric Elliott (@_ericelliott) <a href="https://twitter.com/_ericelliott/status/1269115558479495168?ref_src=twsrc%5Etfw">June 6, 2020</a></blockquote><script async="" src="https://platform.twitter.com/widgets.js" charSet="utf-8"></script><p>Inny wymiar tej rady mówi o tym, że programowanie powinno sprawiać Ci frajdę. Nie bój się napisać &quot;brudnego kodu&quot;, eksperymentuj, baw się. Wykorzystaj różne podejścia. Sprawdź w czym najlepiej się czujesz.</p><p>W programowaniu chodzi o rozwiązywanie problemów. A najpiękniejsze w tym wszystkim jest to, że wiele problemów można rozwiązać na wiele, wiele, wiele różnych sposobów.</p><p>I na koniec - jeśli masz już za sobą ciężki bój przez te &quot;podstawy&quot;, a pisanie każdej kolejnej linijki sprawia Ci ból - to zastanów się czy to na pewno dla Ciebie. Praca programisty nie jest usłana różami, czasami trzeba zanurkować w niemałe szambo i nikt Cię za to po plecach nie poklepie. Z drugiej strony - nie samymi programistami IT stoi. Do branży można wejść na różne sposoby ;)</p><h2>Naucz się świadomie zarządzać czasem</h2><p>Z niecierpliwością czekam, aż mój kolega Radomir, zbierze się w sobie i wygłosi prezentację na temat zarządzania czasem. Sprzedał mi ten temat kiedyś przy jakiejś kawie. Główny przekaz jest mniej więcej taki - jeśli jesteś na studiach to nawet nie wiesz ile masz wolnego czasu. Dopiero (o zgrozo) po czasie dochodzi do nas, ile cennych minut przepaliliśmy. I żebyśmy się dobrze zrozumieli - nie ma nic złego w naparzaniu po nocach w Counter Strike&#x27;a (gra się jeszcze w to?). Nie ma nic złego w imprezowaniu. Jest taki okres w życiu człowieka - studia - gdzie dopiero poznaje się &quot;co to życie&quot;. Korzystaj póki możesz :) Ale pamiętaj - już nigdy nie będziesz miał tyle czasu co teraz. Każdy z nas ma tyle samo czasu. Doba ma 24 godziny. Różnimy się tym, jak ten czas wykorzystujemy. Naucz się swoich nawyków. Swojego organizmu. Sprawdź kiedy jesteś najbardziej produktywny, kiedy najlepiej się uczysz. Wykorzystuj to. Naucz się świadomie zarządzać czasem.</p><p>Ostatnio na Twitterze napisałem, że praca programisty to nieustanna nauka. Tak jest. Na tę naukę też trzeba umieć znaleźć czas. Chcesz prowadzić bloga? Sprawdź ile czasu zajmuje napisanie posta. Kanał na Youtube? Kurs gita, czy babela nagrywam już chyba od lutego :) To nie jest proste. Na wszystko trzeba znaleźć ten cholerny czas. Dlatego Przemku - naucz się świadomie zarządzać czasem.</p><h2>Zainwesuj w umiejętności miękkie</h2><p>Do pierwszej pracy dostałem się na staż. To nawet nie było stanowisko juniorskie. Kiedyś staż kojarzył mi się z parzeniem kawy i wpinaniu kartek do segregatora (tak tak, i jeszcze za to Unia płaciła...ale ciiiiii 🙊). Ale w Samsungu było inaczej. Konkretny projekt, konkretni ludzie, ogrom wiedzy. Dacie wiarę, że startowałem do zespołu C++ (myślałem, że jak kodowałem w tym języku na studiach to się uda) a dostałem się do zespołu SmartTV, w którym pisaliśmy we frontendowych technologiach? W życiu bym nie powiedział. Dla mnie JS kojarzył się tylko z jQuery i śnieżynkami na stronach. No ale nie o tym chciałem pisać.</p><p>Byłem zielony - to fakt. Szybko musiałem nadrobić wiedzę. Udało mi się to dzięki wspaniałym ludziom, z którymi miałem okazję pracować - ale o tym będzie następna rada. Pomimo braku w wielu kwestiach techniczncyh - dosyć szybko awansowałem. Dostałem też możliwość poprowadzenia małego zespołu. Mój szef coś we mnie dostrzegł. Od liceum bardzo lubiłem nawiązywać nowe kontakty. Uważałem się za duszę towarzystwa. Tu coś zagrać na gitarce, tu zagadać, tu się pośmiać. Spotkałem świetnych ludzi na swojej drodze. Byłem szczery, życzliwy, pracowity (teraz też jestem, żeby nie było 😜). Z perspektywy czasu widzę, że to właśnie relacje jakie budowałem z ludźmi pozwoliły mi tak szybko awansować, tak szybko stanąć na czele zespołu.</p><p>Bardzo często nie doceniamy umiejętności miękkich w IT (chociaż zauważam zmieniający się trend). Zdradzę Ci teraz pewien sekret - Przemku - pisanie kodu to nie wszystko. Ba! Umiejętna komunikacja i zdolność do budowania relacji znaczą o wiele więcej niż klepanie kodu.</p><p>Dlatego - zainwestuj w umiejętności miękkie. Sprawdź jak dobrze się komunikować - bo to wcale nie jest takie łatwe.</p><h2>Znajdź mentora</h2><p>W poprzednim punkcie pisałem o świetnych ludziach, z którymi miałem przyjemność współpracować. Szymon K., Grzegorz D., Bartek K., Maciej W. (trochę jak z kryminału :D...no ale nie wiem czy chcieliby się znaleźć tu z nazwiska więc dla pewności zostawiam tak :))...wiele by wymieniać. Wszysktich ich łączy jedno - dążyłem do tego, by być na zbliżonym do nich poziomie. Nie porównywałem się, bo to nie ma sensu. Każdy ma swoją drogę, swoje przeżycia, doświadczenia. Ale możemy się inspirować, czerpać wiedzę, pytać o przeżycia i bazować na doświadzczeniu innych. Dzięki temu zwiększałem prędkość nauki i wchodzenia na wyższy level. Dzisiaj nie wyobrażam sobie pracy w środowisku, w którym nie mogę uczyć się od lepszych od siebie. Zawsze szukam takiej osoby i staram się inspirować.</p><p>PS. Pamiętaj, że to może działać w dwie strony. U kogoś widzisz rzecz, której Tobie brakuje...za to ta druga strona widzi w Tobie rzeczy, które ją inspirują do działania. Dzięki temu wzajemnie się napędzacie.</p><h2>Kup notatnik</h2><p>Wszyscy mówią - załóż bloga, załóż kanał na YT. A ja mówię - kup notatnik. Taki zwykły zeszycik A5 może być. Zapisuj tam skrzętnie to, czego się nauczyłeś. Czytając książki, bądź oglądając kursy - rób notatki. Spisuj swoje osiągnięcia. Zapisuj przemyślenia. Rysuj mapy myśli. To pomaga - serio. Pomaga - ale z czasem. Np. podczas rozmowy o podwyżkę. Takie rozmowy często odbywają się raz do roku. Czy jesteś w stanie z pamięci wyrecytować wszystkie swoje osiągnięcia. Podpowiem Ci - NIE!. Czasami robimy małą rzecz dla siebie, ale wielką dla projektu. Zapisując swoje wszystkie sukcesy i porażki jesteś w stanie popłynąć na takiej ocenie rocznej i wywalczyć upragnioną podwyżkę. Ale nie rób notatek dla pieniędzy :) rób je dla siebie i dla swojego mózgu, który nie zawsze jest w stanie wszystko spamiętać ;)</p><p>PS. Bloga też sobie załóż jak chcesz - dziel się tam swoją nowo-zdobytą wiedzą i doświadczeniem. Tylko proszę - nie nastawiaj się od początku na &quot;zarabianie z bloga&quot;. Nie każ mi akceptować powiadomień i zapisywać się do newslettera :) Na początku bloga prowadź go dla siebie. Sława i splendor przyjdą z czasem....albo i nie :P</p><h2>Doszlifuj angielski</h2><p>W liceum nie byłem jakoś specjalnie zainteresowany angielskim. Tu trójeczka, tam czwóreczka, tu coś ściągnąć, tam zagadać i &quot;jakoś to będzie&quot;. Szybko tego pożałowałem. I choć nie miałem problemu w zrozumieniu słowa pisanego (dokumentacja, książki, maile) to szybko zarobiłem &quot;plaskacza&quot; w momencie, kiedy miałem poprowadzić spotkanie czy prezentację po angielsku. Mówi się, że podstawowym językiem programisty jest język angielski - to prawda. I chociaż coraz więcej publikacji powstaje w języku polskim, tłumaczone są dokumentacje (patrz React) to nadal - posługiwanie się językiem angielskim to podstawa. Bardzo dużo dają tzw. English-Days. W Suncrapers, w którym miałem okazję pracować, był taki jeden dzień - chyba czwartek z tego co pamiętam. W czwartki rozmawiało się tylko po angielsku. To nic, że w projekcie sami polacy. Trzeba było tłumaczyć i szprechać po inglishu. Wiadomo - dla ludzi, którzy dużo się oczytali, ale mało mówili to był problem. Wytłumaczenie prostej rzeczy zajmowało x2, x3 albo x4 tyle co po polsku. Z drugiej strony dzięki takim zabiegom później było łatwiej :)</p><p>Dzisiaj nie mam już problemu z posługiwaniem się językiem angielskim. Cały czas pracuję nad wzbogacaniem słownictwa (aktualnie trenuję z dziećmi Ba-Ba-Black Sheep ;))</p><p>Dlatego Przemku, jeśli to czytasz i chcesz wejść do IT - to wiedz, że bez angielskiego - ani rusz!</p><h2>Podsumowanie</h2><p>I to by było na tyle. Takie rady dałbym <strong>sobie</strong> dzisiaj, gdybym miał zaczynać jeszcze raz w IT. Banalne? Kontrowesyjne? Nudne? Dajcie znać w komentarzach ;)</p><![CDATA[Watch out for fixtures in cypress.io]]>https://przemuh.dev/en/blog/watch-out-for-fixtures-in-cypresshttps://przemuh.dev/en/blog/watch-out-for-fixtures-in-cypressFri, 26 Jun 2020 00:00:00 GMT<p>Today I would like to tell you a story about a bug that cost me two days of searching and debugging sessions. It turned out a trivial thing, and with a better error message, it could have taken seconds instead of days. Let&#x27;s go!</p><h2>Hey Przemek! Could you help me?</h2><p>A few days ago, I noticed that our VRT (Visual Regression Tests) suite started to fail for one case. I&#x27;ve asked my colleague, Monica, to check it. She accepted the challenge. After a long day of searching the root cause, she told me that she doesn&#x27;t have any idea why the test is failing. On the local machine, it has been passing all the time, but on our GitlabCI, we got an error. Weird thing, isn&#x27;t it? Monica was resigned and asked me for help. After two days of trying, committing, pushing, waiting, we&#x27;ve finally found it.</p><h2>Fake server</h2><p>We use a lot of tools in our tests. For unit testing, we use <a href="https://jestjs.io/" target="_blank" rel="nofollow noopener noreferrer">jest</a>. In E2E, we use <a href="https://docs.pytest.org/en/stable/" target="_blank" rel="nofollow noopener noreferrer">py.test</a> with webDriver bindings. We also have UI tests that check our app on a higher level (interactions between components, pages, or views). Recently we introduced another test suite - VRT (Visual Regression Tests). The last two (UI and VRT) are based on <a href="https://www.cypress.io/" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a>. It is an excellent tool for writing tests - from unit to full E2E.</p><p>Backend in our app is very complicated, and it is tough to setup a local environment. Because of that, for UI and VRT tests, we use a killer feature from cypress.io - network stubbing. Cypress can plug in between our app and network request giving us a possibility to decide about the response from API endpoint.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&quot;test with network stubbing&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token comment">// First, we need to start fake server</span> cy<span class="token punctuation">.</span><span class="token function">server</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Next, declare the route that we want to stub</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> value<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>More info about stub responses can be found in <a href="https://docs.cypress.io/guides/guides/network-requests.html#Stub-Responses" target="_blank" rel="nofollow noopener noreferrer">official Cypress documentation</a>.</p><h2>Fixtures</h2><p>Fixtures are another feature from <a href="https://www.cypress.io/" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a> that we use a lot, especially in our VRT suite. A fixture is a simple file that holds the data. We can reuse this file in many places. It helps us in organizing tests and managing the common responses from stubbed network requests. To load a fixture, we use a <code>cy.fixture</code> command. It expects a path to the file that we want to load. The path should be relative to a folder specified to hold fixtures (<code>cypress/fixtures</code> by default). Let&#x27;s assume that we have the following file structure:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">- fixtures - myFixture.json - someSubFolder - mySecondFixture.json</code></pre></div><p>And now let&#x27;s look at code which loads fixtures:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&quot;test with fixtures&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token comment">// We don&#x27;t need to specify the file extension</span> <span class="token comment">// Cypress will try to figure it out</span> cy<span class="token punctuation">.</span><span class="token function">fixture</span><span class="token punctuation">(</span><span class="token string">&quot;myFixture&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">data</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token comment">// Here we can read the data</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// We can save the fixture as an alias ...</span> cy<span class="token punctuation">.</span><span class="token function">fixture</span><span class="token punctuation">(</span><span class="token string">&quot;someSubFolder/mySecondFixture&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">as</span><span class="token punctuation">(</span><span class="token string">&quot;myAlias&quot;</span><span class="token punctuation">)</span> <span class="token comment">// ...and then use the alias in stub of response</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;@myAlias&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>Authors of Cypress took care of reducing a boilerplate needed to use a fixture in stubbing network requests 🔥🔥🔥. The <code>cy.route</code> command can take a shortcut to fixture as a response argument:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/path&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fixture:myFixture&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fx:someSubFolder/mySecondFixture&quot;</span><span class="token punctuation">)</span></code></pre></div><p>In this way, we stubbed a network request with data kept in reusable fixture files. Great job!</p><h2>Where is the hero of the story?</h2><p>Ok, but where did our bug go?</p><p>I&#x27;ve created a simple app to visualize the issue. In the beginning, the app displays the <code>Loading…</code> message, then makes a request and replaces the text with a downloaded response.</p><p>Fetching the data in old, good XHR way 😎</p><div class="gatsby-highlight" data-language="html"><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>main<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Loading...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript"> <span class="token keyword">const</span> mainEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">&quot;#main&quot;</span><span class="token punctuation">)</span> <span class="token keyword">const</span> req <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XMLHttpRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span> req<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token string">&quot;GET&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> req<span class="token punctuation">.</span><span class="token function-variable function">onreadystatechange</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>req<span class="token punctuation">.</span>readyState <span class="token operator">==</span> <span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> msg <span class="token operator">=</span> req<span class="token punctuation">.</span>status <span class="token operator">==</span> <span class="token number">200</span> <span class="token operator">?</span> req<span class="token punctuation">.</span>responseText <span class="token operator">:</span> <span class="token string">&quot;Error&quot;</span> mainEl<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> msg <span class="token punctuation">}</span> <span class="token punctuation">}</span> req<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">&gt;</span></span></code></pre></div><p>I&#x27;ve also written a test:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">&quot;Simple fixture test&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&quot;displays response&quot;</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cy<span class="token punctuation">.</span><span class="token function">server</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fixture:examplefixture&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;#main&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;Hello&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>And created a fixture file <code>fixtures/exampleFixture.json</code>:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Hello</code></pre></div><p>Have you noticed a bug yet?</p><p>In my case, the screenshot from the failed test was very helpful. Cypress takes them by default for failing tests, which is neat 🔥!</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/405343e368f5fd3d144f7527bee68ba2/21b4d/screenshot.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:56.25%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACN0lEQVQoz22SXU/TYBiG+VXCgMFW1nbA9A8YQYmRA6Iy2JgmfhxwQgiHgBxs3UKi8Zd4akKCUT7GBDrGhHVdP1YYePm2AhtokytP3pMrz93n7jIaLsWjU/YrVQ4Eh9VTyhWdsl4im82Sy+VIJpMMRCJIsooUkwNiiooSj9MXHuT5i5fk83mWl1foatVN7I1vuJtfaW5+wdvaoFUqYR78JKtpaIJUKoUsywyPJBgRjI7eJ5F4IGaCqBRjZmaWQqHA6uoqXWcNB2Nnn3pRxzo8wT6u4wiMykmwnS9Mp9MoikJMbBjq7Q/oCfUxFFOIRIdEgpkOoWFS/17E3NqjUdzH1o9xBIZeIesLcxpzc3NIksTY+BO0wjpafp2Pnz4zMfGUvv6B2xteisiXO0UutnfF3KX1Yxt2i2LL6o0wk8kQDofJvHpN5/f23XvudYeYTaXbwgvLxiz/wjyu0bjCrlmYNYOcOIof+a7w95XwzX+FbhPXdjFsD8NyqVsODUdMoy28jvxo7DE5rRDwQfBwfIL+fyK7Lpeug930sGxbyGws174lbB9Fobunl+5QLz1ixq6OMt15lJaIbOhlatUqXsPiXEjPHQfz5DToYacwPjwiqjN6g/8ejESFMNkWepZFvXwk/l2VM0NURohcgb5XYm1tLajOtVCNDyP7hVbjAf47EE5PB8VeWRHFdkTkpufhCpymi/8+a7U41HUWFhZYXFxkamoKVVVvCWXlrzASlXg2OcnS0hLz8/P8AXOxcxI9R2CUAAAAAElFTkSuQmCC&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Screenshot from failed test" title="Screenshot from failed test" src="/static/405343e368f5fd3d144f7527bee68ba2/105d8/screenshot.png" srcSet="/static/405343e368f5fd3d144f7527bee68ba2/3cf3e/screenshot.png 293w,/static/405343e368f5fd3d144f7527bee68ba2/78a22/screenshot.png 585w,/static/405343e368f5fd3d144f7527bee68ba2/105d8/screenshot.png 1170w,/static/405343e368f5fd3d144f7527bee68ba2/21b4d/screenshot.png 1280w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Screenshot from failed test</figcaption> </figure></p><p>And now...Have you noticed a bug yet?</p><p>A message about the status from the stubbed request caught my attention. It was <code>400</code> instead of <code>200</code>. That was a clue.</p><h2>The typo and file systems</h2><p>Our bug, which we&#x27;ve been trying to solve with Monica, was a simple typo. The name of the fixture file was in camelCase, and we tried to load it via shortcut without the same naming convention.</p><p><code>exampleFixture.json</code> vs <code>cy.route(&quot;/api&quot;, &quot;fixture:examplefixture&quot;)</code></p><p>Ok, but why does it work on the local machine and doesn&#x27;t on CI?</p><p>99% of our frontend team works on MacBooks. Our CI runs the tests in the docker container (Linux). You can think - &quot;so what?&quot;. The default file system on Linux is case sensitive. On the other hand, the default file systems on Mac or Windows are not. What does it mean in practice?</p><p>On Linux you can create two files with the &quot;same&quot; name (different letter case):</p><ul><li>myAwesomeFile.js</li><li>myawesomefile.js</li></ul><p>Linux treats them as separate files. Try to do the same on Mac or Windows - you can&#x27;t do it. It has also impact on the way how you load the files, for example in nodejs. On Mac, there is no difference in load file by &quot;myFixture&quot; or &quot;mYFiXtURe&quot; names - the file will be loaded. On Linux, we will get an error - file not found.</p><h2>Let&#x27;s check it</h2><p>If we modify the code of our test in this way:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fixture:ExAmPlEFiXTuRe&quot;</span><span class="token punctuation">)</span></code></pre></div><p>The test is always green on Mac. On Linux we get a <code>400</code> status for stubbed network request and an error message in console.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/9541d7f7ab7da3ac37cb220e4e54e35a/c211c/stub.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:55.9254327563249%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABBElEQVQoz52R3W6DMAxG8/5XILjlKRBXPEq10VGtLeWvZMQJlPCNqNDSat1glo4sJfaR47AkLXDIKmQZR5Jz5EUFUZ9RFDk45xBCoK5rKKXQti2apnnAnJk7U2dgaVbiY3dEyQm1vECqoam90nUdpuj7/lemYF03SCRB6wt6rYes73ksnDe8iknMiAscNzHSaIdT9Ikk2iPdHnBOigfhUljDvyDftqAovvMeQ50y6BUT3p4siECNAqk5EjSsYdrhKiGNv/OMOZ8LFz+ZxuYbZuJRaoRrZFehETwhRvF/gv0kk1KiqiqEYQjf9xEEwSJM7UthWZbwPA+2bcNxnD9xXReWZeEbYpJEWPqWc/gAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Screenshot with 400 for stubbed request" title="Screenshot with 400 for stubbed request" src="/static/9541d7f7ab7da3ac37cb220e4e54e35a/105d8/stub.png" srcSet="/static/9541d7f7ab7da3ac37cb220e4e54e35a/3cf3e/stub.png 293w,/static/9541d7f7ab7da3ac37cb220e4e54e35a/78a22/stub.png 585w,/static/9541d7f7ab7da3ac37cb220e4e54e35a/105d8/stub.png 1170w,/static/9541d7f7ab7da3ac37cb220e4e54e35a/c211c/stub.png 1502w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Screenshot with 400 for stubbed request</figcaption> </figure></p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">CypressError: The following error originated from your application code, not from Cypress. When Cypress detects uncaught errors originating from your application it will automatically fail the current test. This behavior is configurable, and you can choose to turn this off by listening to the `uncaught:exception` event. https://on.cypress.io/uncaught-exception-from-application </code></pre></div><p>Wait, wait, wait...WAT? The following error originated from your application code, not from Cypress. Are you sure Cypress? 🤔</p><p>Let&#x27;s try to load the fixture without a shortcut:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token comment">// We made a mistake in fixture name</span> cy<span class="token punctuation">.</span><span class="token function">fixture</span><span class="token punctuation">(</span><span class="token string">&quot;examplEFixture&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">as</span><span class="token punctuation">(</span><span class="token string">&quot;response&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;@response&quot;</span><span class="token punctuation">)</span> <span class="token comment">// With storing fixture in an alias we can use it in our assertions</span> <span class="token comment">// We don&#x27;t need to hardcode the &quot;Hello&quot; string</span> cy<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;@response&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">data</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> cy<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;#main&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>The error message for this code is quite different:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Error: A fixture file could not be found at any of the following paths: &gt; cypress/fixtures/examplEFixture &gt; cypress/fixtures/examplEFixture{{extension}} Cypress looked for these file extensions at the provided path: .json, .js, .coffee, .html, .txt, .csv, .png, .jpg, .jpeg, .gif, .tif, .tiff, .zip Provide a path to an existing fixture file.</code></pre></div><p>And this is the error message that I&#x27;ve been counting on 👏 . We know right the way where we should start looking 😎.</p><h2>Summary</h2><p>There are two takeaways from this story:</p><ul><li>small typo could make you cry for two days of debugging session</li><li>you are as good as the error message from your test runner ;)</li></ul><p>I think that Cypress could return the better message about missing fixtures than <code>CypressError</code>. That&#x27;s why I&#x27;ve created an issue in cypress GitHub repository - <a href="https://github.com/cypress-io/cypress/issues/7818" target="_blank" rel="nofollow noopener noreferrer">here you can check the status</a>.</p><p>Thank you for your attention. I am going to try to solve the issue that I&#x27;ve created 😉. Maybe I will be able to add something to the OpenSource community to make cypress.io even better 😁</p><![CDATA[Stop the time with cy.clock]]>https://przemuh.dev/en/blog/stop-the-time-with-cyclockhttps://przemuh.dev/en/blog/stop-the-time-with-cyclockWed, 22 Apr 2020 17:00:00 GMT<p>Today I’m going to show you how to stop the time with one command. Unfortunately only in <a href="https://cypress.io" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a> tests. If you know how to do it in real life please DM me. It would be a very helpful tip 🙂. Ok, let’s stop joking and get our hands dirty!</p><h2>App description</h2><p>First, we need to have something to test. Our app will be deadly-simple. We want to display the enter time and a counter shows how many seconds we spent in the app.</p><div style="text-align:center;margin:2em 0;border:1px solid;padding:2em"><p>Enter time: <span data-testid="enter-time"></span></p><p>Time on page: <span data-testid="counter">0</span></p></div><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">&quot;react&quot;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>enterDate<span class="token punctuation">,</span> setEnterDate<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>counter<span class="token punctuation">,</span> setCounter<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">setEnterDate</span><span class="token punctuation">(</span>Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">const</span> intervalId <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">setCounter</span><span class="token punctuation">(</span><span class="token parameter">prev</span> <span class="token operator">=&gt;</span> prev <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">clearInterval</span><span class="token punctuation">(</span>intervalId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div<span class="token operator">&gt;</span> <span class="token operator">&lt;</span>p<span class="token operator">&gt;</span> Enter time<span class="token operator">:</span> <span class="token operator">&lt;</span>span data<span class="token operator">-</span>testid<span class="token operator">=</span><span class="token string">&quot;enter-time&quot;</span><span class="token operator">&gt;</span><span class="token punctuation">{</span>enterDate<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span> <span class="token operator">&lt;</span>p<span class="token operator">&gt;</span> Time on page<span class="token operator">:</span> <span class="token operator">&lt;</span>span data<span class="token operator">-</span>testid<span class="token operator">=</span><span class="token string">&quot;counter&quot;</span><span class="token operator">&gt;</span><span class="token punctuation">{</span>counter<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span></code></pre></div><p>Ok, we have our app. Now it is time to write some cypress tests.</p><h2>We are testing!</h2><p>In our test scenario we would like to check:</p><ul><li>if the enter time is displayed properly,</li><li>if the counter increases its value after a one-second tick.</li></ul><p>Let&#x27;s try this way:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>The test looks decent but it doesn’t pass 😢</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:569px"> <a class="gatsby-resp-image-link" href="/static/8ea52e51054d035a2d33cf2a73475a17/854dc/datenow-assert-error.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:50.08787346221442%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABxklEQVQoz42S627TQBCF8/4PUZWW8g4gIQFtkFIgKGnTxEnbOHHs3G92fNnr4Xg3CIk/YOnTWY9nxme00/hyeY35ZoP8dIK1FlpraGOcmrP+C6WUy83zHI2vF1cQUiA7HlGVJYqicFpSCyYYFvzPI0SF3W6HRvPiGnXJ7phin2UopUJBSqXdudIGwlhiPPyBQ535/c687ESHzcu3UEJiO3hG/NBHsdoimyZIw4gaI62ZeM2iBPly42Geoz4vNiio2XbPhlfvYHYH6M4T5MdbiA+foTo9lHctyO8dVO8/Qd7do2reQ7Ta0I8BdOsnFGPqtgXdfoB6HMD2higXKzZ8cwOTnaCCF6ghoVM5GEH2R1CjMeSPrudbB6r/DEP3ZhyRKcwrCWeMxTDMLZYr71DvDxDtLkT3yanqBcB8RdYAxwLHwWINGy9gZ3Oy8MRnTVacoIXTJDo7zAuIIZ2NJ1CzBPI1dIhghKofuLgMJ9B0oGYxVJz4vHDqvomXMW91j5KX6hvydiTHlePQF7OZL/CNVDSD5RSWm2APxz/sjz5OkHJD0tTfsq0XeLmGnEaQtC2jGJaLjnoHuTqQEqgqoPyLymPdWaBkzS92HfDmxAi65wAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Assertion error" title="Assertion error" src="/static/8ea52e51054d035a2d33cf2a73475a17/854dc/datenow-assert-error.png" srcSet="/static/8ea52e51054d035a2d33cf2a73475a17/3cf3e/datenow-assert-error.png 293w,/static/8ea52e51054d035a2d33cf2a73475a17/854dc/datenow-assert-error.png 569w" sizes="(max-width: 569px) 100vw, 569px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Assertion error</figcaption> </figure></p><p>If we would like to show some formatted date (eg. 22-04-2020) instead of a number of milliseconds, then it would not be a problem. But our client wants to display milliseconds and we need to live with this requirement 😉</p><p>The <code>cy.clock</code> command comes with a rescue. It overrides native global functions related to time allowing them to be controlled synchronously via <code>cy.tick()</code> or the yielded <code>clock</code> object. This includes controlling:</p><ul><li><code>setTimeout</code></li><li><code>clearTimeout</code></li><li><code>setInterval</code></li><li><code>clearInterval</code></li><li><code>Date</code></li></ul><p>You can find more info about cy.clock in the cypress.io <a href="https://docs.cypress.io/api/commands/clock.html" target="_blank" rel="nofollow noopener noreferrer">official documentation</a>.</p><p>Now, let’s try to add <code>cy.clock</code> to our test:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>We still get an error. But this time the error message is different.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">expected &lt;span&gt; to have text &#x27;1587547901669&#x27;, but the text was &#x27;0&#x27;</code></pre></div><p>What is going on with that <code>0</code>? Because the time represented in timestamp value is a number of seconds passed from the start of Unix epoch (1st January 1970). We could ask what will happen after the 19th of January 2038 but this is a topic for another blog post 🙂.</p><p>Calling <code>cy.clock</code> without any arguments sets the date in our app to 1st January 1970. We could change it by passing an argument to the <code>cy.clock</code>:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span></code></pre></div><p>Right now, with a little luck, our test will pass. It depends on how fast our computer is 😄. To fix this issue we need to remember that <code>cy.clock</code> overrides the time in our app, not in our tests (command chain). That’s why we need to change <code>Date.now()</code> in our assertion to <code>now</code> value that we’ve created at the beginning of the test.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> now<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>The test is green - always! - success! But there is one little difference in how our app works now. Before using <code>cy.clock</code> our timer has been running. Right now it stops on <code>0</code>. Fortunately, it is expected behavior in our test-case scenario. We&#x27;ve set and stopped the time.</p><p>In order to move the time with some value, we need to call <code>cy.tick</code> command:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> now<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=counter]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;0&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">tick</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=counter]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;1&quot;</span><span class="token punctuation">)</span></code></pre></div><p>Tada 🎉! We&#x27;ve just wrote the test checking the enter date and the value of the counter.</p><h2>What if we would like to set the date only - without stopping the time? 🤔</h2><p>That’s a great question. Sometimes we would like to override the <code>Date</code> object only, leaving the rest untouched (<code>setTimeout</code>, etc.). In this case, we need to pass a second argument to the <code>cy.clock</code> - an array of timing functions that we want to override.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>Date<span class="token punctuation">.</span><span class="token constant">UTC</span><span class="token punctuation">(</span><span class="token number">2020</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">22</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">&quot;Date&quot;</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre></div><p>In this example we set the date/time to 22th April 2020 00:00 UTC (yeap - months in <code>Date</code> starts from <code>0</code> that&#x27;s why April = <code>3</code> 🙂). In the same time we don&#x27;t override the <code>setTimeout</code> and the rest time functions.</p><hr/><p>That&#x27;s all for today. I hope that with this knowledge you can go now and stop the time in your tests 😉</p><p>Good luck!</p><![CDATA[3 Steps to Awesome Test Reports with Cypress]]>https://przemuh.dev/en/blog/3-steps-to-awesome-test-reports-with-cypresshttps://przemuh.dev/en/blog/3-steps-to-awesome-test-reports-with-cypressWed, 18 Dec 2019 00:00:00 GMT<p>In this article, you will learn how to generate informative test reports with Cypress and how to enrich them with some screenshot context. This will help you to fix your potential bugs way faster 😄 All you need is three simple steps.</p><h2>At Egnyte we ❤️ to test</h2><p>Keeping the highest possible quality of the product is one of our top priorities at Egnyte. That&#x27;s why we love to test. But our applications are rather large and relying on manual testing would be exhausting for us. That&#x27;s why test automation and Continuous Integration techniques are our best friends. We write a lot of tests: unit, integration, end-to-end, module, etc. The most important part is that, at the end of the day, if our Jenkins pipeline is green, we are sure that we didn&#x27;t break any parts of the system.</p><p>So where is the problem? Didn&#x27;t you know that tests not always pass? And that&#x27;s fine :) We don&#x27;t need to panic right away. First, let&#x27;s calm down, get into the test report on Jenkins, check what is broken, and fix it. That&#x27;s it. <strong>The problem is that the test report is very often just a plain error message plus a stack trace.</strong> And it&#x27;s enough for unit tests or integration tests for our React components, redux connections, and so on. On the other hand, this is not always helpful for tests that are run in the browser. Let&#x27;s imagine the following result from a test:</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/888b4da9d98b5f76f424ce227440d090/29007/jenkins.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:44.3125%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAABDUlEQVQoz2VQi3KDIBD0/3+x8YGiBwfFaKNoFNJVO7Y2OzfLzTJ7r8Q5R0Te+xDCuuNIlmVZ3xCuSNq27bruuQOGk89knud38aiVWGvR3PsR8H7aMG84HiiHf/+YzloHEniMMUopIkXakW4lOeJ7rVyjXU2bQtyC4YoxXsZGAWZuiIuSb7lOC05LcysQ/JFzJiyYbT/657KGGF+IE5sZnbVmWatKKlFpIS1KZMKkYuOjImbhz/4xTLjjrxmbwMz75Jq5JlNKjWiUrWpGsL279su63rg+/u0LM9RSci6UkDovVVpQJhSSosIiyliHA4cQ1/Vi+zFjbzTHmXHYYZwRj2Eed8aQYChYGEqI//3fzUwBr8ba6BcAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Jenkins console output" title="Jenkins console output" src="/static/888b4da9d98b5f76f424ce227440d090/105d8/jenkins.png" srcSet="/static/888b4da9d98b5f76f424ce227440d090/3cf3e/jenkins.png 293w,/static/888b4da9d98b5f76f424ce227440d090/78a22/jenkins.png 585w,/static/888b4da9d98b5f76f424ce227440d090/105d8/jenkins.png 1170w,/static/888b4da9d98b5f76f424ce227440d090/29007/jenkins.png 1600w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Jenkins console output</figcaption> </figure></p><p>I took this failed test report from our data governance product (<a href="https://www.egnyte.com/protect/content-governance-solution.html" target="_blank" rel="nofollow noopener noreferrer">Egnyte Protect</a>), which is one of our core products. For writing integration-UI tests, we use an awesome tool called <a href="https://cypress.io" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a>. I must admit that Cypress and <a href="https://github.com/testing-library/cypress-testing-library" target="_blank" rel="nofollow noopener noreferrer">cypress-testing-library</a> are doing an excellent job in terms of error messages. Judging by the test report shown above, it is clear that we cannot find an element with matching text. Of course. But what is the visual state of the app? As a developer of Egnyte Protect, I know that this message should appear in a dialog. Has this dialog been opened? Or maybe it is only a typo? So many questions and no answers. If we wanted to check it, we would need to run the test locally once again and see what the visual state of the app is. Only then we would know (spoiler alert) that we have a typo :).</p><p>What if we displayed the visual state of the app right in the Jenkins report?</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/6b320f1a3bdfadd990a910edb663855e/f793b/app.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:54.487179487179496%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABYElEQVQoz42Q2U7CQBSG+yosGr0FSmm8o8x0ppoobVktEhCe0bfQxDsiILK0oJUlYLWrDhaIgYjOfHMuTv4vZ2aoY5Y9YtkwwxwwzGEiQWooFgtEo8E1gUgkTNNB0qTpUDxOMhsoxAsICRh9VywIWEhfpCVR8pFlOUO2JOPzixOIIMJ+0leoFEQreMwBKIri7d19t6c12912pz9Qn7Wh/vI61UZ6uVJJcinA441CAYh8IOkCmMlkHvsD0/Fcx/38sSzbrtfrHJcisY2yLZPLqqpK0p67kj3PW8qWVavX/pAlSdK04cb5rwwgBoDP5XK97pNpWfPF24dp2Y5LjuO4hvFeq+2RSRfAfD7f6zT18Yz80GS2WBimabtktG3tffNyMuSz2WzjoTVfGOPJjKCPp9PJtKmNbhqtavWafPZv8hKEhUJRUUrlS+VKWVMoluSCIpyebYW3ZUKSA7twHNhNfgEqfifvvlhbZQAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Screenshot from the app" title="Screenshot from the app" src="/static/6b320f1a3bdfadd990a910edb663855e/105d8/app.png" srcSet="/static/6b320f1a3bdfadd990a910edb663855e/3cf3e/app.png 293w,/static/6b320f1a3bdfadd990a910edb663855e/78a22/app.png 585w,/static/6b320f1a3bdfadd990a910edb663855e/105d8/app.png 1170w,/static/6b320f1a3bdfadd990a910edb663855e/f793b/app.png 1404w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Screenshot from the app</figcaption> </figure></p><p>Wow! Now we know that the dialog is opened, and that the subheader text is incorrect! We have some valuable context just from reading the test report enriched with a single screenshot.</p><p>So how we could add screenshots to our test reports? Let&#x27;s find out!</p><h2>HTML reports to the rescue!</h2><p>Cypress is based on <a href="https://mochajs.org/" target="_blank" rel="nofollow noopener noreferrer">mocha.js</a>. And this is great because mocha.js is a very mature project with many custom extensions. Test results can be generated within elements called reporters. We can write our custom reporter or use an existing one, for example, <a href="https://www.npmjs.com/package/mochawesome" target="_blank" rel="nofollow noopener noreferrer">mochawesome</a>. As the name suggests, it generates <strong>AWESOME</strong> reports! Badum tsss.</p><p>And now, I would like to show you how to integrate <a href="https://www.npmjs.com/package/mochawesome" target="_blank" rel="nofollow noopener noreferrer">mochawesome</a> with <a href="https://cypress.io" target="_blank" rel="nofollow noopener noreferrer">cypress</a> to generate HTML reports with a screenshot context for failed tests. For the sake of this blog post, I&#x27;ve used an example repo <a href="https://github.com/cypress-io/cypress-example-kitchensink" target="_blank" rel="nofollow noopener noreferrer">cypress-example-kitchensink</a>. We will do it in 3 simple steps. Let&#x27;s get our hands dirty!</p><h2>Step 1 - set up the reporter</h2><p>First, we need to install proper reporters. Yes, that&#x27;s right - plural - reporters. We still want to see test results in the console. Maybe you also want to have a JUnit XML report. We need to have one reporter per expected outcome (console, HTML, XML). In order to set up many reporters, we will use the <a href="https://www.npmjs.com/package/cypress-multi-reporters" target="_blank" rel="nofollow noopener noreferrer">cypress-multi-reporters</a> package. On top of that, we also need <code>mocha</code> and, of course, <code>mochawesome</code>.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> --save-dev mocha cypress-multi-reporters mochawesome</code></pre></div><p>Or if you use <code>yarn</code>:</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> -D mocha cypress-multi-reporters mochawesome</code></pre></div><p>Then, in the <code>cypress.config</code> file, we need to specify which reporter we want to use:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">&quot;reporter&quot;</span><span class="token operator">:</span> <span class="token string">&quot;cypress-multi-reporters&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;reporterOptions&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;configFile&quot;</span><span class="token operator">:</span> <span class="token string">&quot;reporter-config.json&quot;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div><p>The <code>configFile</code> field points to the reporters configuration file. We need to add this file to our repository. For each of the reporters we can specify some options. Let&#x27;s do that for the mochawesome reporter:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">&quot;reporterEnabled&quot;</span><span class="token operator">:</span> <span class="token string">&quot;mochawesome&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;mochawesomeReporterOptions&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;reportDir&quot;</span><span class="token operator">:</span> <span class="token string">&quot;cypress/results/json&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;overwrite&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;html&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;json&quot;</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div><p>In this fragment of config, we specify an output directory for the results file. We want to collect only the JSON files for each spec file. That&#x27;s why the <code>html</code> flag has been set to false. Because cypress is able to run tests in parallel, we need to set the <code>overwrite</code> flag to <code>false</code>. It means that for each spec file, we will generate a separate file. In our case, these will be JSON files.</p><p>Let&#x27;s try to run our tests via <code>npm run local:run</code> command.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">Running: examples/location.spec.js <span class="token punctuation">(</span><span class="token number">9</span> of <span class="token number">19</span><span class="token punctuation">)</span> Location ✓ cy.hash<span class="token punctuation">(</span><span class="token punctuation">)</span> - get the current URL <span class="token builtin class-name">hash</span> <span class="token punctuation">(</span>169ms<span class="token punctuation">)</span> ✓ cy.location<span class="token punctuation">(</span><span class="token punctuation">)</span> - get window.location <span class="token punctuation">(</span>101ms<span class="token punctuation">)</span> ✓ cy.url<span class="token punctuation">(</span><span class="token punctuation">)</span> - get the current URL <span class="token punctuation">(</span>78ms<span class="token punctuation">)</span> <span class="token number">3</span> passing <span class="token punctuation">(</span>1s<span class="token punctuation">)</span> <span class="token punctuation">[</span>mochawesome<span class="token punctuation">]</span> Report JSON saved to /Users/przemuh/dev/cypress-example-kitchensink/cypress/results/json/mochawesome_008.json</code></pre></div><p>As you can see, after the spec reporter results, we received information that the <code>mochawesome_008.json</code> file has been created. Each of the spec files generated a JSON with results.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:652px"> <a class="gatsby-resp-image-link" href="/static/581d079e46b5dd429410b52138039577/dba9a/list.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:142.0245398773006%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAIAAADuuAg3AAAACXBIWXMAAAsSAAALEgHS3X78AAAB90lEQVQ4y5WU227TQBCGc1PUCiHuUGkpEHsTJz7Eu7PrdcINRa3UCNRKXPEMiEtS1Mu+QHlmfnuSSULSrjsarSaHz/+cvL2XX/+8+nb77sd9/euv+/nw+vruxdXvw/niCT+aLw6uFm9ubntn7uJ9dalmc33xPf18fUpf3urzE9Oc7Ih3/bg8H36a98aDKB2qIh04M5lk40T1RypqvY+f4AiSuD+MPmy6+nimi7SXJKMkGWudkM0MuaqqfF27ylvr+Ky8z/I8jpVSA/EoistSA4aNAFubE1kYeJzUfnDOGYOUJnFLi0VRVJblEjYGcEaEPzfizOCs6xqPCMArZSdG7QfOYi+ML9cwUW4MSdpQ5hhP6ahsuU6p2XuPoCiKXTjP8yVclksYIADmWRnxrjLbVsM4bTatNbDpdLpXeQtG2s4VzWRXxm3jzPNmzk/CxmRaGyJ4Y1DmtNG5MMyjYikeFQzx3lFtwWgY0bJmzlZGhTNcM5Q3YVbmOMuycLdlPXk32BCnadpFWSZlBcbjOsHtkjhZEl4yKAe6LQ2TOk1rzD9DGa+hKHMLAsqr9bT/jYr3LNBtXs+2wGZD5TLhFoQ3DDVrTVwkTi6Ys+hY8/oa2sw8AMv7LO+wiHdSXu32Om2+A8M1o9veFx5Xdl3PZjPcAdw5PEg9bv8A9dAma9wEJYgAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="List of generated results" title="List of generated results" src="/static/581d079e46b5dd429410b52138039577/dba9a/list.png" srcSet="/static/581d079e46b5dd429410b52138039577/3cf3e/list.png 293w,/static/581d079e46b5dd429410b52138039577/78a22/list.png 585w,/static/581d079e46b5dd429410b52138039577/dba9a/list.png 652w" sizes="(max-width: 652px) 100vw, 652px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">List of generated results</figcaption> </figure></p><p>We are ready to go to the next step.</p><h2>Step 2 - generate the report</h2><p>We&#x27;ve collected the test results. Now, we need to merge them into one file and generate an HTML report based on it. We will use the <a href="https://www.npmjs.com/package/mochawesome-merge" target="_blank" rel="nofollow noopener noreferrer">mochawesome-merge</a> tool to merge result files. Let&#x27;s install it.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> i --save-dev mochawesome-merge <span class="token function">yarn</span> <span class="token function">add</span> -D mochawesome-merge</code></pre></div><p>Now, let&#x27;s add an npm script which will be responsible for running the merge tool.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report:merge&quot;: &quot;mochawesome-merge --reportDir cypress/results/json &gt; cypress/results/mochawesome-bundle.json&quot;</code></pre></div><p>The <code>reportDir</code> flag specifies where we keep results files. The output of the command is passed from stdout to the <code>mochawesome-bundle.json</code>. One caveat here: the result of the merge needs to be put in a different folder than where the single results file is.</p><p>After merging we are ready to generate a final HTML report. In this case, we will use <a href="https://www.npmjs.com/package/mochawesome-report-generator" target="_blank" rel="nofollow noopener noreferrer">mochawesome-report-generator</a>.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> i --save-dev mochawesome-report-generator <span class="token function">yarn</span> <span class="token function">add</span> -D mochawesome-report-generator</code></pre></div><p>Let&#x27;s create an npm script for that action:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report:generate&quot;: &quot;marge cypress/results/mochawesome-bundle.json -o cypress/reports/html&quot;</code></pre></div><p>Marge is a short form of <strong>M</strong>och<strong>a</strong>wesome<strong>R</strong>eport<strong>GE</strong>nerator in case you&#x27;ve been wondering :)</p><p>Once the script has run, our awesome HTML report should appear in the <code>cypress/results/html</code> folder.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/c44c00ff45ade76717090ba27ef04155/29007/reporter.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:55.6875%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAAA80lEQVQoz5WR227CMAyG+/IDaRcUuoNE+niM0m6Q2M45abd5RWJotEL7ZOUi8qf8dorHl9e1qFdbsdhUD+vNonpaVs/Ln3OiuLkUotyKsq4rIYqcU0ohpxhz0J6+7pFS5H6umGIBgGcUwOHUdvL9qI6oUTtDlqyzbiSOBO/xAmBBFzQ13f7tY8eyQvDehxjiFZyQT7riV0YiCScyZJzxwecppmVNWqJssbOGc9rPGTj2rNzIhiex1g7DcGvylbdWz8l72RAQ76Lv+0k5ODcfG1qOzTKPx7/yj9gKVTfOjIS82Ek5hjD78kG1hjQAnMe+5U/sb8tlYyeUKfRVAAAAAElFTkSuQmCC&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="HTML report view" title="HTML report view" src="/static/c44c00ff45ade76717090ba27ef04155/105d8/reporter.png" srcSet="/static/c44c00ff45ade76717090ba27ef04155/3cf3e/reporter.png 293w,/static/c44c00ff45ade76717090ba27ef04155/78a22/reporter.png 585w,/static/c44c00ff45ade76717090ba27ef04155/105d8/reporter.png 1170w,/static/c44c00ff45ade76717090ba27ef04155/29007/reporter.png 1600w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">HTML report view</figcaption> </figure></p><p>There is one more thing to do. Add a screenshot to tests that have failed.</p><h2>Step 3 - add screenshot context</h2><p>Cypress automatically generates screenshots for failed tests in the <code>cypress/screenshots</code> folder. You can disable this behavior if you want. Screenshots are collected within the following folder structure:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">path-to-the-specfile/spec.file.js/context - describe - describe - testTitle (failed).png</code></pre></div><p>For example, the following test placed in examples/actions.spec.js:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">context</span><span class="token punctuation">(</span><span class="token string">&#x27;Actions&#x27;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">context</span><span class="token punctuation">(</span><span class="token string">&quot;nested context&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&#x27;.type() - type into a DOM element&#x27;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>will generate something like this on fail:</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1056px"> <a class="gatsby-resp-image-link" href="/static/08b26a7260e306a2e5536e34e45fad84/fd84e/folder-structure.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:17.424242424242426%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAIAAAAcOLh5AAAACXBIWXMAAAsSAAALEgHS3X78AAAAVklEQVQI15WOUQrAMAhDe5Y6LLaKrNr7X20ZZfvbx95HEDGJZYwx51xrmRkRHX8orTV3jwikMDN9U2t9ddfc5tPRnPBnpqoipfcuIhi2Auzxmj7gAOYL0ZwcBDCNqUcAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Folder with screenshot from failed test" title="Folder with screenshot from failed test" src="/static/08b26a7260e306a2e5536e34e45fad84/fd84e/folder-structure.png" srcSet="/static/08b26a7260e306a2e5536e34e45fad84/3cf3e/folder-structure.png 293w,/static/08b26a7260e306a2e5536e34e45fad84/78a22/folder-structure.png 585w,/static/08b26a7260e306a2e5536e34e45fad84/fd84e/folder-structure.png 1056w" sizes="(max-width: 1056px) 100vw, 1056px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Folder with screenshot from failed test</figcaption> </figure></p><p>Ok, so how we can connect these two elements: a screenshot generated by Cypress and a test result generated by a mochawesome reporter?</p><p>First, let&#x27;s copy our generated screenshots to the folder where we keep the HTML reports. In order to do this, we will use an npm script:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report:copyScreenshots&quot;: &quot;cp -r cypress/screenshots cypress/results/html/screenshots&quot;</code></pre></div><p>Next, we will use the cypress/support/index.js file and write some code that will be listening on the <code>test:after:run</code> event.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">Cypress<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">&quot;test:after:run&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">test<span class="token punctuation">,</span> runnable</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>test<span class="token punctuation">.</span>state <span class="token operator">===</span> <span class="token string">&quot;failed&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// do something</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>For adding the screenshot to the test result, we need to use the addContext method from the <code>mochawesome/addContext</code> package. This method takes two arguments: an object with the test, and the context. If the context is a valid URL (could be a local path) to the image, then that image will be displayed. To see more details, visit the <a href="https://www.npmjs.com/package/mochawesome#adding-test-context" target="_blank" rel="nofollow noopener noreferrer">documentation page</a>.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> addContext <span class="token keyword">from</span> <span class="token string">&#x27;mochawesome/addContext&#x27;</span> Cypress<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">&quot;test:after:run&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">test<span class="token punctuation">,</span> runnable</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>test<span class="token punctuation">.</span>state <span class="token operator">===</span> <span class="token string">&quot;failed&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> imageUrl <span class="token operator">=</span> <span class="token string">&quot;?&quot;</span><span class="token punctuation">;</span> <span class="token function">addContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span> test <span class="token punctuation">}</span><span class="token punctuation">,</span> imageUrl<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>Ok - but how to define the <code>imageUrl</code>? This is a time for magic to happen.</p><p><img src="https://media.giphy.com/media/12NUbkX6p4xOO4/giphy.gif" style="width:50%;margin:auto;display:block"/></p><p>Just kidding :) we will use the runnable object. As we saw earlier, Cypress generates the name of the screenshot based on the test suite structure. We need to re-create that.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">Cypress<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">&#x27;test:after:run&#x27;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">test<span class="token punctuation">,</span> runnable</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>test<span class="token punctuation">.</span>state <span class="token operator">===</span> <span class="token string">&#x27;failed&#x27;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> item <span class="token operator">=</span> runnable <span class="token keyword">const</span> nameParts <span class="token operator">=</span> <span class="token punctuation">[</span>runnable<span class="token punctuation">.</span>title<span class="token punctuation">]</span> <span class="token comment">// Iterate through all parents and grab the titles</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>item<span class="token punctuation">.</span>parent<span class="token punctuation">)</span> <span class="token punctuation">{</span> nameParts<span class="token punctuation">.</span><span class="token function">unshift</span><span class="token punctuation">(</span>item<span class="token punctuation">.</span>parent<span class="token punctuation">.</span>title<span class="token punctuation">)</span> item <span class="token operator">=</span> item<span class="token punctuation">.</span>parent <span class="token punctuation">}</span> <span class="token keyword">const</span> fullTestName <span class="token operator">=</span> nameParts <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">&#x27; -- &#x27;</span><span class="token punctuation">)</span> <span class="token comment">// this is how cypress joins the test title fragments</span> <span class="token keyword">const</span> imageUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">screenshots/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> Cypress<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>name <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>fullTestName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> (failed).png</span><span class="token template-punctuation string">`</span></span> <span class="token function">addContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span> test <span class="token punctuation">}</span><span class="token punctuation">,</span> imageUrl<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>From now on, if our test fails, a context field with a local URL to the image will appear in the JSON results file:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">&quot;title&quot;</span><span class="token operator">:</span> <span class="token string">&quot;.type() - type into a DOM element&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;fullTitle&quot;</span><span class="token operator">:</span> <span class="token string">&quot;Actions .type() - type into a DOM element&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;timedOut&quot;</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">&quot;duration&quot;</span><span class="token operator">:</span> <span class="token number">10395</span><span class="token punctuation">,</span> <span class="token property">&quot;state&quot;</span><span class="token operator">:</span> <span class="token string">&quot;failed&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;speed&quot;</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">&quot;pass&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;fail&quot;</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token property">&quot;pending&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;context&quot;</span><span class="token operator">:</span> <span class="token string">&quot;screenshots/examples/actions.spec.js/Actions -- .type() - type into a DOM element (failed).png&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">}</span></code></pre></div><p>What is more, the image itself will be attached to the HTML report.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/8fd9e50f05c93dd52922645fba1b999e/29007/reporter-error.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:55.49999999999999%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABuklEQVQoz41S21LCMBTs/z+rI6CgUvSZYQb+Qn1BcBS8l0tL0iZtAq1kPUmRQeXBzOxs0p5s9ly8arOJ+tUVjs/PcXBaJ5zisFLBYb2O6kUTJ34LNd9HjeIqdK42fYejxpmLP2o0UGu1zFnrEv6Frz0YA6zXcGyX5eUSeZY5FEqj0LqM+b1srMPalEejPcY5OMEyY3bPEM3nGI2f8Tgab/H2/gEex4iTBGmaOjDGynucm5j+kY72EgrYQiSQiQCLQkyiKSIWkQCJyBhCCmTkWJHb1WoFTWzvCCGIhZFSWl569oPDRlTSPpxN8BEFYAknAYW8yEvkJYqi+CWYGPtgFC3MT8ENC0olzVIqpXYXFdWxdJZDU30tMqW+xUojUpowDLFXUJF9LiRkplzN3t6nmIcLSlnZtCh9SQ9mu4IuZRLc7zCTKUaTKcYBCS0YgsmcBJmrIXXSYUkud1LGRnC/Q81jJA9PEPcjCHKmVwXWnz/Hxtbx34KKWNI4GGpAOJvh+uYWd3cDDIdDDAYD9Pt9BEHgRmenhn8F3Ws0T5oaYptg18vrKzqdDnq9HrrdruN2u+1EbcPs/O0KfgFmmzLlPg5ZHQAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="HTML report with screenshot context" title="HTML report with screenshot context" src="/static/8fd9e50f05c93dd52922645fba1b999e/105d8/reporter-error.png" srcSet="/static/8fd9e50f05c93dd52922645fba1b999e/3cf3e/reporter-error.png 293w,/static/8fd9e50f05c93dd52922645fba1b999e/78a22/reporter-error.png 585w,/static/8fd9e50f05c93dd52922645fba1b999e/105d8/reporter-error.png 1170w,/static/8fd9e50f05c93dd52922645fba1b999e/29007/reporter-error.png 1600w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">HTML report with screenshot context</figcaption> </figure></p><p>TADA 🎉 We got it!</p><p>You can check all necessary code changes that we have done in the following pull request: <a href="https://github.com/przemuh/cypress-example-kitchensink/pull/1/files" target="_blank" rel="nofollow noopener noreferrer">https://github.com/przemuh/cypress-example-kitchensink/pull/1/files</a></p><h2>Optional steps</h2><p>You might want to add <code>cypress/results</code> and <code>cypress/reports</code> folders to your <code>.gitignore</code>.</p><p>It would be good to remove screenshots, results, and reports before the next test run. It can be done by a simple npm script. In our example repo, I&#x27;ve added:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;precy:run&quot;: &quot;rm -rf cypress/screenshots cypress/results cypress/reports&quot;</code></pre></div><p>&quot;Pre&quot; means that this script will be run before every <code>cy:run</code>. See <a href="https://docs.npmjs.com/misc/scripts" target="_blank" rel="nofollow noopener noreferrer">npm docs</a> for more details.</p><p>In the example repo, there is a <a href="https://www.npmjs.com/package/npm-run-all" target="_blank" rel="nofollow noopener noreferrer">npm-run-all</a> package installed. We could use it to run in sequence: merge, generate report and copy screenshots scripts in one command:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report&quot;: &quot;run-s report:*&quot;, &quot;report:merge&quot;: &quot;mochawesome-merge --reportDir cypress/results/json &gt; cypress/results/mochawesome-bundle.json&quot;, &quot;report:generate&quot;: &quot;marge cypress/results/mochawesome-bundle.json -o cypress/reports/html&quot;, &quot;report:copyScreenshots&quot;: &quot;cp -r cypress/screenshots cypress/reports/html/screenshots&quot;</code></pre></div><p>There is also one caveat. The file name in most systems is limited to 255 characters. So what will happen when we have a very nested structure of a test suite with long descriptions? It&#x27;s simple - our file name will be truncated. Cypress truncates the full test name to 220 characters. So we could also do the same in our code:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">MAX_SPEC_NAME_LENGTH</span> <span class="token operator">=</span> <span class="token number">220</span><span class="token punctuation">;</span> <span class="token keyword">const</span> fullTestName <span class="token operator">=</span> nameParts <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">&quot; -- &quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token constant">MAX_SPEC_NAME_LENGTH</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>But this is an implementation detail. We don&#x27;t know whether Cypress devs are about to change that number. So, a better option would be to read an article from <a href="https://kentcdodds.com/" target="_blank" rel="nofollow noopener noreferrer">Kent C Dodds</a> about <a href="https://kentcdodds.com/blog/avoid-nesting-when-youre-testing" target="_blank" rel="nofollow noopener noreferrer">avoiding nesting when you are testing</a>.</p><h2>Wrap-up time</h2><p>I hope that this article will help you to set up awesome HTML reports in your project. It helps us a lot when it comes to quick investigations of why a given test is failing. Let&#x27;s recap what we did here:</p><ol><li>Install and set up the mochawesome reporter.</li><li>Collect test results and generate an HTML report based on the merged JSON file</li><li>Add screenshot context with an <code>addContext</code> function.</li></ol><p>You can check all code changes here in this <a href="https://github.com/przemuh/cypress-example-kitchensink/pull/1/files" target="_blank" rel="nofollow noopener noreferrer">pull request</a>. And of course, after you generate the HTML report you need to connect it somehow to your Continuous Integration tool. But this is a story for a separate post. :)</p><p>Now…are you ready to create your own Cypress HTML reports?</p>
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>
<![CDATA[ przemuh.dev [EN] ]]>
</title>
<description>
<![CDATA[ Learn, Code, Teach, Repeat. JavaScript dev's blog. Tutorials/courses about frontend related stuff, but not only. ]]>
</description>
<link>https://przemuh.dev</link>
<generator>GatsbyJS</generator>
<lastBuildDate>Fri, 01 Jan 2021 13:11:27 GMT</lastBuildDate>
<item>
<title>
<![CDATA[ Short story about optimisation ]]>
</title>
<description>
<![CDATA[ This article has not been translated yet. Here is a Polish version. Stay tuned Jakiś czas temu na Twitterze zamieściłem zrzut ekranu pokazujący flame-chart z narzędzia Profiler. Pracowałem wtedy nad… ]]>
</description>
<link>https://przemuh.dev/en/blog/tree-performance-improvement-case-study</link>
<guid isPermaLink="false">https://przemuh.dev/en/blog/tree-performance-improvement-case-study</guid>
<pubDate>Sun, 08 Nov 2020 00:00:00 GMT</pubDate>
<content:encoded><p><em>This article has not been translated yet. Here is a Polish version. Stay tuned</em></p><p>Jakiś czas temu na <a href="https://twitter.com/przemuh/status/1319595759935852544" target="_blank" rel="nofollow noopener noreferrer">Twitterze zamieściłem zrzut ekranu</a> pokazujący flame-chart z narzędzia Profiler. Pracowałem wtedy nad poprawą wydajności aplikacji, którą rozwijamy w Egnyte. Pewna funkcjonalność, dla dużej ilości danych, zajmowała strasznie dużo czasu - 3,5 minuty! Przez ten czas w aplikacji pokazywany był &quot;kręciołek&quot;, a użytkownik nie wiedział, czy coś się dzieje, czy może coś się zawiesiło. Po kilku dniach pracy z Profilerem udało mi się zaimplementować poprawki, które zredukowały czas potrzebny do obliczeń z 3,5 minuty do 35 sekund. W tym wpisie chciałbym opisać jak do tego doszedłem.</p><h2>Opis funkcjonalności</h2><p>Zacznijmy od opisu funkcjonalności, która krótko mówiąc kulała pod względem wydajności. Jednym z głównych widoków w naszej apce jest tzw. &quot;Sensitive Content&quot;. Pokazujemy tu listę folderów z plikami, które zawierają wrażliwe dane. To mogą być numery kart kredytowych, dane medyczne, personalne i nie tylko. Dane te mogą podchodzić pod jedną z kilkunastu wbudowanych polityk np. HIPA, GDPR, ale również dajemy możliwość użytkownikom zdefiniowana własnej polityki np. w oparciu o utworzony wcześniej słownik. Początkowo widok &quot;Sensitive Content&quot; pokazywał tylko płaską listę folderów. W zeszłym roku nasz Product Owner wraz z zespołem UX doszli do wniosku, że praca z płaską listą folderów może być nieefektywna. Zamiast tego dużo lepszym, i w zasadzie bardziej naturalnym sposobem reprezentacji danych, będzie drzewko folderów.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:944px"> <a class="gatsby-resp-image-link" href="/static/f622f9b398ed8964cce4a32ffc9df3fb/966a0/sc-view.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:37.81779661016949%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABVElEQVQoz01R2U7EMBDr//8fLwiEuLTskftq0rTGk3YXIllJp45jz0yXm8ZVGdy0HZDz9aidQ8YpFqg0o7WOMjcEflufYGyAdRGGcCGN3biAyVqHmBJiTCQTIaKUGb2vqEtH6x25LThbD2Pc4NXaBjfngllQK7wPRMSktIY2li9aPM6EDwELBWVF7s907J0b/9uyjHrnYygVKWUopZFLwfR+0vg8G3xdLD5+NL4vvBRm2NTgckcsK66x4Ymcb3KEa2JFYN2wHnzBxSS8nxSuNmO6PRxSSBu6NIwTUVvD0neHiQ5f2V9HjrhvbMG2YSTYeh7RJZ20ao98ILGXYjvnPCI3isrKh6B30g5Dob/I21oHXymFeaagECx70w83/9d27OLwhYLBezhy772VBH3dhhER3B2q3Z0QJY5MeWOeO4ZDXnzjhJ3dB3Yfijjt64pEh/qI/AvbE2lTetVs3wAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Widok Sensitive Content List" title="Widok Sensitive Content List" src="/static/f622f9b398ed8964cce4a32ffc9df3fb/966a0/sc-view.png" srcSet="/static/f622f9b398ed8964cce4a32ffc9df3fb/3cf3e/sc-view.png 293w,/static/f622f9b398ed8964cce4a32ffc9df3fb/78a22/sc-view.png 585w,/static/f622f9b398ed8964cce4a32ffc9df3fb/966a0/sc-view.png 944w" sizes="(max-width: 944px) 100vw, 944px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Widok Sensitive Content List</figcaption> </figure></p><h2>Algorytm budowania drzewa</h2><p>Podejść do drzewek było już u nas w projekcie kilka. Głównie opierały się one na własności folderu jakim było unikalne <code>folderId</code>. Niestety, w przypadku &quot;Sensitive Content&quot; nie mogliśmy tego użyć, ponieważ nie wszystkie foldery mogły zawierać wrażliwe dane, a tylko dla takich folderów dostawaliśmy <code>folderId</code>.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">/Shared/A/B/ W tym wypadku mamy 3 foldery (Shared, A, B), z czego tylko dla B dostajemy folderId</code></pre></div><p>Takich lokacji z SC (Sensitive Content) może być multum. Problem wydajności pojawiał się już przy 250 tysiącach lokacji. A że nie jest to wyjątek utwierdził nas klient, u którego znaleźliśmy prawie <strong>milion</strong> folderów. To właśnie dla 1M elementów listy, czas budowania drzewka wynosił 3,5 minuty. Dlatego też, moje zadanie polegało na tym, że drzewko dla 1M elementów ma się budować w mniej niż 60s.</p><p>Wracając do algorytmu. Prosty - jak budowa cepa - tak by się wydawało :)</p><ol><li>Weź całą ścieżkę i podziel ją na fragmenty wg. separatora np. <code>/</code></li><li>Każdy folder wsadź do dwóch struktur: &quot;drzewiastej&quot; i &quot;płaskiej&quot;</li><li>Jeśli folder nie ma <code>folderId</code> traktuj go jako meta-folder</li></ol><p>Dla uproszczenia pomijam kwestię tego, że wspieramy różne źródła danych i te separatory mogą się mocno różnić :) Co więcej, jak się później okazało, niektóre źródła danych mogą mieć dwa foldery o tej samej nazwie, na tym samym poziomie zagnieżdżenia 😱. I jak je rozróżnić? Pominę też kwestię tego, że wynikowe drzewo mieliśmy przedstawić w formie &quot;sparse-tree&quot;. W skrócie - chodzi o to, że jeśli folder zawiera tylko jeden sub-folder, to ścieżka rodzica powinna być zwinięta/scalona.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Lista: /Shared/A/B/C /Shared/A/B/D --&gt; Drzewo: /Shared/A/B /C /D</code></pre></div><h2>Pierwsza, a w zasadzie druga implementacja</h2><p>Jak już wspomniałem wcześniej, nie było to pierwsze drzewo, jakie mieliśmy wyświetlić w aplikacji. W zupełnie innym widoku, też mieliśmy zrobić sparse-tree i nie chcieliśmy mieć kilku różnych implementacji. Dlatego napisaliśmy prosty moduł do budowania i zarządzania drzewkiem. Oparty został o dwa małe komponenty:</p><ul><li>funkcję <code>buildTree</code>, która przyjmowała płaską tablicę węzłów i miejsce (ścieżkę w drzewie) od którego miała te węzły wstawiać</li><li>&quot;plasterek&quot; (slice) z <code>redux-toolkit</code>, który zarządzał strukturą drzewa (rozwijanie, zwijanie węzłów itp.)</li></ul><p>Całe drzewo, a w zasadzie te dwie struktury &quot;drzewiasta&quot; i &quot;płaska&quot;, trzymane były w reduxie w następujący sposób:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> tree<span class="token operator">:</span> <span class="token punctuation">{</span> path<span class="token operator">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span> <span class="token comment">// root</span> children<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;Shared&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token comment">// path-part or folder name as a key</span> path<span class="token operator">:</span> <span class="token string">&quot;/Shared&quot;</span><span class="token punctuation">,</span> children<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;A&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> path<span class="token operator">:</span> <span class="token string">&quot;/Shared/A&quot;</span><span class="token punctuation">,</span> children<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> paths<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;/Shared&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> meta<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> ...nodeProps <span class="token punctuation">}</span> <span class="token property">&quot;/Shared/A&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> meta<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> folderId<span class="token operator">:</span> <span class="token string">&quot;some-unique-id&quot;</span><span class="token punctuation">,</span> ...nodeProps <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div><p>Taką strukturę otrzymujemy z pomocniczej funkcji <code>buildTree</code>.</p><p>Dzięki zastosowaniu <a href="https://redux-toolkit.js.org/" target="_blank" rel="nofollow noopener noreferrer">redux-toolkit</a>, a co za tym idzie biblioteki <a href="https://github.com/immerjs/immer" target="_blank" rel="nofollow noopener noreferrer">immer</a>, mogliśmy w bardzo prosty sposób wykonywać operacje na drzewie:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> initialTreeState <span class="token operator">=</span> <span class="token punctuation">{</span> initialized<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> tree<span class="token operator">:</span> <span class="token punctuation">{</span> path<span class="token operator">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span> children<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> paths<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">createTreeSlice</span> <span class="token operator">=</span> <span class="token parameter">treeName</span> <span class="token operator">=&gt;</span> <span class="token function">createSlice</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token operator">:</span> treeName<span class="token punctuation">,</span> initialState<span class="token operator">:</span> initialTreeState<span class="token punctuation">,</span> reducers<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token function-variable function">insertTree</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> tree<span class="token punctuation">,</span> paths <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> payload <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> paths <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>paths<span class="token punctuation">,</span> <span class="token operator">...</span>payload<span class="token punctuation">.</span>paths<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> node <span class="token operator">=</span> <span class="token function">getNodeByPath</span><span class="token punctuation">(</span>payload<span class="token punctuation">.</span>parentPath <span class="token operator">||</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span> tree<span class="token punctuation">)</span> node<span class="token punctuation">.</span>children <span class="token operator">=</span> payload<span class="token punctuation">.</span>tree<span class="token punctuation">.</span>children <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">toggleNode</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> tree <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> payload<span class="token operator">:</span> path <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> node <span class="token operator">=</span> <span class="token function">getNodeByPath</span><span class="token punctuation">(</span>path<span class="token punctuation">,</span> tree<span class="token punctuation">)</span> node<span class="token punctuation">.</span>expanded <span class="token operator">=</span> <span class="token operator">!</span>node<span class="token punctuation">.</span>expanded <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>Pomocnicza funkcja <code>getNodeByPath</code> służy do wyszukiwania węzła po ścieżce. Potrafi też wyszukać węzeł w sparse-tree.</p><h2>Pierwsze podejścia do optymalizacji i pierwsze błędy</h2><p>No i wszystko pięknie-ładnie, ale przyszedł klient, 1 milion folderów i jebs... Product Owner zakłada Epic w Jirze pt. &quot;Support 1M folders on SC tree view&quot;. Szybka burza mózgów i od razu kosz pełen pomysłów:</p><ul><li>a może by tak budować drzewo w locie, jak parsujemy JSONa?</li><li>a może by tak budować drzewo w web-workerze, przynajmniej nie zablokujemy głównego wątku na 3,5 minuty?</li><li>a może by tak, pizgnąć to wszystko i wyjechać w Bieszczady? ⛰ 🐑</li></ul><p><img src="https://media.giphy.com/media/kPtv3UIPrv36cjxqLs/giphy.gif" alt="A może by tak..."/></p><p>Pierwszy błąd - nikt nawet nie odpalił Profilera, żeby zobaczyć co zajmuje tyle czasu. Każdy założył, że obecna implementacja drzewka wymiata i lepiej być nie może. Sam Profiler na pierwszy rzut oka nie jest prostym narzędziem i może to było powodem tego, że rzuciliśmy się wtedy na tego typu pomysły jak budowanie drzewa &quot;w locie&quot; czy przeniesienie tego do web-workera. Warto też podkreślić, że sam Epic w Jirze powstał już jakiś czas temu, a do samej implementacji usiadłem w połowie października.</p><p>Pewnie zastanawiacie się - ale jak to w locie? Przecież jak idzie request to dopiero jak przyjdzie odpowiedź to przeglądarka parsuje JSONa i daje odpowiedź. Tak, ale...w tym przypadku nasi backendowcy też musieli podziałać trochę w kwestii optymalizacji i zamiast zwracać pełne dane to zaczęli tę naszą listę SC zwracać na zasadzie stream&#x27;u. Dzięki temu mogliśmy np. wykorzystać bibliotekę <code>oboe.js</code> to parsowania JSONa w locie.</p><p>Oczywiście spróbowałem tego podejścia, no bo w końcu ktoś to wpisał do zadania w Jirze, trzeba było sprawdzić co nie? 😜 Fajnie, JSON parsował się &quot;w locie&quot;, ale stream zamiast 10s trwał 30s, a jeszcze nie zacząłem nawet budować drzewa. Dlatego odpuściłem i postanowiłem poszukać gdzie indziej.</p><h2>Web-worker</h2><p>Podejście z web-workerem też przetestowałem. Ale tu napotkałem zupełnie inny problem. Ok - mogę sobie pobrać 1M elementów i zbudować na tej podstawie drzewo, ale muszę je później przesłać z wątku web-workera do wątku głównego. Struktura drzewa jest dość obszerna, razem z danymi, które zapisywaliśmy w tej płaskiej strukturze <code>paths</code>. Jeśli chcemy przesłać tak duże dane z jednego wątku do drugiego, przeglądarka musi te dane zserializować, przesłać, a następnie ponownie sparsować. To też powodowało &quot;zamrożenie&quot; przeglądarki na czas przesyłania z jednego miejsca pamięci do drugiego. Oczywiście są sposoby na przesłanie &quot;bezpośrednie&quot; (bez kopiowania) poprzez tzw. Transferable Objects np. ArrayBuffer, ale uznałem, że na chwilę obecną to może być gra nie warta świeczki i postanowiłem sprawdzić, czy faktycznie ta nasza implementacja drzewka była tak zajebista jak myśleliśmy 😜</p><h2>Profiler podejście pierwsze</h2><p>Usiadłem przed ekranem komputera, odpaliłem dev-toolsy i wcisnąłem przycisk &quot;record&quot; w Profilerze. Po chwili dostałem taki kolorowy wykresik, który przypomniał mi czasy defregmentatora dysków z Windowsa 98 🤣</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/08d444b93bebf165ed87c320556ad7b0/d9ed5/flame-graph-violet.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:62.5%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACOklEQVQ4y42TeW/aQBDF/ZGrtiSUw6Fqv1YVVYqapkkgKeEU4JgjGBx87YkPXscODUhVpP7x05ud3X07s14blVoNpZMyTkunqFNcr5+h8qmKWtWEaZoUn1BcRqV8gtL7d4V+bpiom1V8+dpA46yGRr0Ks1ZB6cNHGH7oYRMQvgsmQ3AtIWIFGWtwFUGLGRK5QKZWhINMOkjEEimR8KeXmHIsWMB1HRhaawjBCQHORaFZtiMAEiT4f6TSMISkipR6hdPY91YIwhW8pQ1/0odvDY7owytyR9A4sIeIgmcy1Cm1loEVpGS6g+V00N58w8PwHL2L75i2bmC3rmE3fxHXmDZvMGs1C83npve3sK+usB6Nc8MMx8jtDjxMEW1iOP0N2pcPGA8tPPanGC8eMVpN0Jn30G33MLq3MGiO0G8OYd8uEFn6X8MCqlbSBfJRDP/So5MdrAdLMKapiy08yWB3uljcD2HddTG9HsJtrhAtGQz5hqFIdoieNKJbutt2DP5bQ/Jd0YGKgfWFB+88gPfTQ/SDgbVo3Uq9YZgTU4XPCuGYI5qRzvIPlu7nUoQ9ynUkVaURWqQTCeVu32g5p7jLLdgsAnMUGJ0uXg2JeQLxRJXzhA6mTuYcMoipwu3hY+QcDAmZgW+24B498g291/wV/F0TUByRKRkKMmKugqKxkS/KnwuTdJJKD9CmosWQNjAah8khn2ueEy8IRnNkKsncWLvP8IMQYcgKGP0tjMu9EmxPJA654zw/mif9A0zFwtIb7Iv6AAAAAElFTkSuQmCC&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Flame graph" title="Flame graph" src="/static/08d444b93bebf165ed87c320556ad7b0/105d8/flame-graph-violet.png" srcSet="/static/08d444b93bebf165ed87c320556ad7b0/3cf3e/flame-graph-violet.png 293w,/static/08d444b93bebf165ed87c320556ad7b0/78a22/flame-graph-violet.png 585w,/static/08d444b93bebf165ed87c320556ad7b0/105d8/flame-graph-violet.png 1170w,/static/08d444b93bebf165ed87c320556ad7b0/28884/flame-graph-violet.png 1755w,/static/08d444b93bebf165ed87c320556ad7b0/92bee/flame-graph-violet.png 2340w,/static/08d444b93bebf165ed87c320556ad7b0/d9ed5/flame-graph-violet.png 2880w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Flame graph</figcaption> </figure></p><p>Pierwsze co rzuciło mi się w oczy to ten fioletowy kolor, który zanurkował bardzo, bardzo głęboko. Po bliższym spojrzeniu okazało się, że bardzo dużo tych fioletowych elementów to sprawka <code>immer.js</code>. Szybki rzut oka w dokumentację i boom! strzał w dziesiątkę. Okazuje się, że przy &quot;wkładaniu&quot; dużej ilości danych poprzez <code>immer</code> możemy przyspieszyć ten proces poprzez <code>Object.freeze</code> <a href="https://immerjs.github.io/immer/docs/performance#performance-tips" target="_blank" rel="nofollow noopener noreferrer">tutaj więcej info</a>. Zabieg ten pozwolił mi na zejście z 12,54s na 11,24s dla 54K elementów. Dla 1M skok był oczywiście proporcjonalnie większy. No ale to nadal nie było to...</p><h2>Od profilera, do źródeł</h2><p>Wiedzieliście, że jak klikniecie na dany blok w Profilerze, a następnie przeniesiecie się do pliku, to dostaniecie czasy dla poszczególnych bloków kodu? Nie!? 😎 To teraz już wiecie ;)</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/1ba2423b674f0cab158baa993f4c7cfd/0d0e4/source-before.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:90.73170731707317%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAABYlAAAWJQFJUiTwAAACUElEQVQ4y4VUa5eaMBT0//+39kPrqa26qy4LKpDwSMgbpje40kWtyzEnYJLJ3Llz76IVEnnJwOsGQjokSYbt5oDdrsLbW4PDniHLzhBCw1mLEAL6Pozz59H3Pbz3WKiug2pPMLJEfAJtbniH/a7BdluMF7CyhlIe3nkMw4D/PRF0IaWEqFICPeO6d9CeQC04N7DWwxqLrnOQ0tDcEaN+BP48JkClFDr+AtMmuN7t6PCv5RHr9ZHY8ZHZeNENwO08AlrS5fqMCx+LjRBYbzZYrTLSMicN238AGGahzwCN0RDnn+jYetqgRYdjXiIrK7SyRtM044gHHjGbAWqtwZJvYOmPWVh1XaEoKuz3pLGwDwEeMlRKozkt0RZXhgN62pBmDWW6wOnIiZ2C1u4S7ICZjneAMWuyyaElnwDjjzMxejDPFYwOs8MDnoRsjCHDasqkmRYMvdddBWko3M7AeLJPuIxn2Z5sI8s/ZJ3dxMJqg/2hwPJ3gn1yQHpieKHvnNfwwd/pNwMUZA+tFZxzc23CgNMrozIssXvleE84+bKhyqnHJFkbHjOMgN65O00CMbFUbm3r0IpYKZYyb+j7UjVPASWFq9tsCsO7AEtAog3ExoymjsBKhbv6vQs5ZrlKv0MWq2kTq6g5pGTs/EQGrynTsYYVot7Rt0+TErMcG4ORbGoOhiQoOEdZMZRMwhjqND7qdhm3zO4qxRlB1ukuf9KiI6vwQiF9F6h59CKjqiGtffi6UiJDbxUCeS82zlgRmkTvKcvOXZqm9z0x6z9qeXga8l+P/3vSoyCcKQAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Czasy przed optymalizacją dla buildTree" title="Czasy przed optymalizacją dla buildTree" src="/static/1ba2423b674f0cab158baa993f4c7cfd/105d8/source-before.png" srcSet="/static/1ba2423b674f0cab158baa993f4c7cfd/3cf3e/source-before.png 293w,/static/1ba2423b674f0cab158baa993f4c7cfd/78a22/source-before.png 585w,/static/1ba2423b674f0cab158baa993f4c7cfd/105d8/source-before.png 1170w,/static/1ba2423b674f0cab158baa993f4c7cfd/0d0e4/source-before.png 1230w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Czasy przed optymalizacją dla buildTree</figcaption> </figure></p><p>To co się rzuca w oczy to 229ms dla zbudowania prostego ciągu znaków 🤯 jakim jest aktualna ścieżka. Okazało się, że to zwykłe niedopatrzenie można było zastąpić krótszym kawałkiem kodu, który koniec końców zajmuje 1.7ms.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/831c9d5d2fd63e39d71b3e58d9daf5e6/7be33/source-after.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:100.38510911424905%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAABYlAAAWJQFJUiTwAAACtElEQVQ4y41UCXKjMBDM/3+XrdojTuIb29ygCx1AbyMn2HFc3hWeMqURrZ6ZnnnSWsO0B1h5hDYeSZJi8bbG66rELlFIU4X1ukR6KuCsRfAeIQT0fX/XnrrOwtQbiGKJcQSc86gKgdWyxvNzjsVii90uRVlpdMZhHHo8WgQ06HQTbQKMqx/gtEfdOGjdEcjC8yJjRkZh4L2Lx0Z+cGtPrdT8uESnigg4YY6uR5tbbLcSmn4hFNqmRVFUmFJU14zKhBn0C0OhOgSnyEDPzuAcDmmOZZLhVOaRVSs6hq2glCPDAT709wGnkEWxQnV6PR+YnmGEIZP9IcPLosHxKBmBh0wNLFPxuW7BPgA7aBalyd6/HFLM3W6bsCAK253k/hAvus3ZdS4joGN4482NPWXRNBZt+7iid0M2ZmK4onTWc8jTLy8r/HxfY01dJnmJQ3HE+pjwP0clKxjPVFnxnaHWBrp8h66WFycfpRWrKVgIi4omBN9LhaqkHqnda3Y3gDwgc/S2mTedt8iqCvuiQNHmaFSDjLJpuhqs7wcIvuXxHDKL0qa/UCY/Lg6abh3eXjL8+bPBZpNhuSxYHAJnilq0AMa7eYwMvVUsRDeDffp1aZBS3JudZi/XrHpLYEFgEYs2ibtnV31hqJRi/9qzLK5CmAaA8z3zFWCtp5gD33u+c5+dZMzZNww3gHHaNAk6ccCllQcETQAZIFoKWkpKSMfedi48lE7Moch+M7yXedN0HqeixiGrkbI4U5Xr2pGRj5FMYU52zW5mOElAlwvI/ALoOF0K9vJ6V2GbNMxvT5Zky36WUpCtiwMiBP8d0HJoOttF+9wcOCjrusN+ryGFY96mEWbIKvxPp5gI1gd/aT3myZDF6WQ4sjpOGDtP6X/28sQwHvxwTNVU0sZBEMIQbRjGhxPmev0FrkwWn/TxJDQAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Czasy po optymalizacji dla buildTree" title="Czasy po optymalizacji dla buildTree" src="/static/831c9d5d2fd63e39d71b3e58d9daf5e6/105d8/source-after.png" srcSet="/static/831c9d5d2fd63e39d71b3e58d9daf5e6/3cf3e/source-after.png 293w,/static/831c9d5d2fd63e39d71b3e58d9daf5e6/78a22/source-after.png 585w,/static/831c9d5d2fd63e39d71b3e58d9daf5e6/105d8/source-after.png 1170w,/static/831c9d5d2fd63e39d71b3e58d9daf5e6/7be33/source-after.png 1558w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Czasy po optymalizacji dla buildTree</figcaption> </figure></p><p>Pomyślicie - (ironicznie) wow... 227ms... &quot;brawo Ty&quot; 👏. Czym jest 227ms? Jeśli spojrzymy na to jako pojedynczą wartość - to fakt...mikro-optymalizacja. Ale pamiętajcie, że celem było obsłużenie 1M elementów, a operacja sklejania ścieżki dotyczyła każdego sub-folderu.</p><h2>Mój spread operator AKA Object.assign taki piękny</h2><p>Jak zrobić płytką kopię obiektu, bądź rozszerzyć inny obiekt - nic prostszego - spread operator <code>...</code>. Jeśli musicie wspierać przeglądarki takie jak IE11, to pewnie korzystacie z babel.js - tak jak my...no i taki spread operator, koniec końców jest tłumaczony na <code>Object.assign</code> (w wielkim uproszczeniu).</p><p><code>Object.assign</code> jest <a href="https://twitter.com/dan_abramov/status/980436488860196864" target="_blank" rel="nofollow noopener noreferrer">stosunkowo wolny</a> i przy większej skali może sprawiać problem. W tym przypadku postawiłem na zwykłe kopiowanie per klucz. Dzięki temu prostemu zabiegowi zbiłem 154ms do 44ms. I znowu, dla pojedynczych elementów to nie ma absolutnie żadnego znaczenia, ale kiedy iterujemy po dużym zbiorze danych takie optymalizacje mogą zdziałać cuda.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/7bed4f76daac0b292144d25c8a68996a/35252/dan.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:93.85382059800665%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAACuklEQVQ4y51U23LaQAz1//9EX/sTfehDp5k2MwkJ2MQ2EDBgx1eM7xc41dnYCcn0kunOyNrVylrp6OxqztLC9MdX7F0Pi+UKy9UKZVXhfD6jP51w+qDQv2k7aPrNNb58/oQkKxBGMYIwRtf3+J/BwFpelNh7T1ivN9hsHGy3O+x2e9FbZXOcLWzbhuu6UoUrPuLnOFg9Pirf9XoNPwhUQGapeZ6HyWQCXdcxm82UXi6XsCwLhmFgOp2KnuPu7h6mKbb5XPx03Iv99naCB9NE0zTPGTKg++Tj+maCqW7AeLCgmzbm1gKTqRzAtfEASzLkj7a9gCkH2YuFCsr1TBKwLBur1SOOxwzaNm3wc5djHpSY+hWMoIIZVrBEzKiGn7c4CaYj+P0wH3XXdUrGtVa2PZL6jLwDDlWPtutl8zTI+3n/zt4rJnB+Op2V1jg5RD6i4AlVWaJt25dT/yVt+zov5d+6bqARzOvvV7j6doVDekSapiiKAnleoKrqD1MmyzJUdQ2NnCvLSoKlCtRIuBgnCZLkoORwoKRIqOXA+FghFc7SHsWx0v3A20aq04gBjfu9C0/4mOe54NgpKlAIAWWcN4IxS73cJ//ko+ZaO3RoHGEYIhiIykEnHkSMuHeUShKpgBVdjvMYkCVXcnd931c/UFgym0WnWrIIo0gBH4mmL/dZyRjoTYYKQwHf9Xx4QvIgiGTdCMCtEnU1XYGiqGQ/ECyPyieKkz8EFEzYbt5HPwjVA0FnNoANyrJc2ZlhEEZqzQypLwM+vzZsipCRFNk4O3kI5LLLA7GTBq03W8FKKFTKbfFDybZRL1GWC5ZRglQY8X6wWRo/zLBu2mc9zJtB3tjry3nz4vPiS2Kzy4XwkDgym1HTlg/2egz8N5HD1APLUpI0QyE/UmdS/pHElZJiaUA53JZLrH4rg88vUpezjjrk5acAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Dan Abramov o Object.assign" title="Dan Abramov o Object.assign" src="/static/7bed4f76daac0b292144d25c8a68996a/105d8/dan.png" srcSet="/static/7bed4f76daac0b292144d25c8a68996a/3cf3e/dan.png 293w,/static/7bed4f76daac0b292144d25c8a68996a/78a22/dan.png 585w,/static/7bed4f76daac0b292144d25c8a68996a/105d8/dan.png 1170w,/static/7bed4f76daac0b292144d25c8a68996a/35252/dan.png 1204w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Dan Abramov o Object.assign</figcaption> </figure></p><h2>Agregacja wartości</h2><p>Po &quot;podkręceniu&quot; immera i wyrzuceniu kliku Object.assign, bądź przepisaniu ich na prostą pętlę, skończyły mi się pomysły na &quot;proste&quot; optymalizacje. Trzeba było podkręcić sam sposób budowania drzewa.</p><p>Poprzednia implementacja, dzieliła sobie SC lokacje wg. źródła i dla każdego z nich budowała pod-drzewo. Dla każdego pod-drzewa liczone były zagregowane wartości (np. jeśli folder sam w sobie zawierał 10 SC, ale miał dodatkowo 50 sub-folderów, chcieliśmy pokazać zsumowane wartości). Dla każdego takiego pod-drzewa, wykonywane były sumowania, a następnie węzeł źródła był aktualizowany wg. zsumowanych wartości dla wszystkich folderów.</p><p>Każda taka operacja wrzucała coś do stanu w redux&#x27;ie. Pomyślałem - a na co to komu? A komu to potrzebne? Przecież nie pokazujemy drzewa dopóki wszystko nie jest policzone i zaktualizowane. Dlatego, zmieniłem kod tak, aby najpierw zbudować w pamięci całe drzewo wraz z policzonymi zagregowanymi wartościami, a następnie za pomocą jednej operacji, wsadzić zbudowane drzewo do reduxa.</p><p>Co więcej - w wymaganiach drzewka, było napisane, że pewne węzły miały być domyślnie rozwinięte np. pierwszy poziom + potencjalnie wcześniej zaznaczony element na liście (z listy do drzewka można przejść za pomocą prostego przycisku). Wcześniej operacje rozwijania były wyzwalane za pomocą akcji <code>toggleNode</code>. To też zmieniłem - zamiast odpalać reduxową akcję, po prostu zmieniam wartość <code>expanded</code> na <code>true</code> bezpośrednio w obiekcie węzła.</p><p>Można zapytać - co Ci to dało drogi Panie?</p><p>Dla 54K elementów zjechałem z czasu 12,25s na 2,4s 🚀</p><p>Łogień w szopie :) Product Owner cały w skowronkach.</p><p><img src="https://media.giphy.com/media/ciwIz38tlvDFH08Yuu/giphy.gif" alt="Wow"/></p><h2>Testy dla 1M</h2><p>Poprosiłem backendowców, żeby przygotowali mi środowisko do testów dla 1M elementów. Chciałem sprawdzić czy moje optymalizacje dla 54K znajdą uzasadnienie. No i uśmiech nie uciekł z mej twarzy :)</p><p>Przed optymalizacją czas budowania drzewa wynosił ~3,5 minuty. Po zaaplikowaniu wyżej wymienionych zmian udało się zjechać do 59s.</p><p>Mniej więcej ~70% oszczędności. W sumie można by powiedzieć - job done - miało się budować w mniej niż 60s... 59 to mniej niż 60 😅 Jest git...</p><p>Trochę byłem już zmęczony tym grzebaniem w de-facto w nie swoim kodzie...ale kumpel z zespołu słusznie stwierdził:</p><blockquote><p>No fajnie, fajnie, ale dla mnie nadal to jest wolno.</p></blockquote><p>Dodał też potem, że nic mi nie ujmuje i wg. niego wykonałem kawał dobrej roboty...ale trudno było się z nim nie zgodzić. Od momentu kliknięcia w element nawigacji, do czasu wyświetlenia widoku, użytkownik musiał poczekać łącznie 90s:</p><ul><li>25s pobieranie danych (streaming)</li><li>6s przeglądarka parsuje JSONa</li><li>59s budowanie drzewa</li></ul><p>Jako użytkownik, gdybym przez 90s widział tylko spinner (&quot;kręciołek&quot;) to by mnie szlag trafił :) Nie chcę myśleć, co czuli nasi użytkownicy jak musieli czekać 3,5 minuty...pewnie żaden nie wytrzymał 😅</p><p>...no...ale wracając...kumpel mówi: &quot;wolno&quot;...no to ja od razu: &quot;co!? wolno!? ja Ci pokażę!&quot; 🤣</p><h2>Generowanie ID</h2><p>Znowu zanurkowałem w Profiler. Dla meta-folderów generowny był <code>folderId</code>. Było to spowodowane tym, że inne miejsce w kodzie tego <code>id</code> potrzebowało (mniejsza o to). Koniec końców to generowane id nic nie znaczyło (nigdy nie było wysyłane do backendu). Ktoś jednak wymyślił, że to meta-folder-id ma być haszem ze ścieżki...</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">createUniqueIdForLocation</span> <span class="token operator">=</span> <span class="token parameter">path</span> <span class="token operator">=&gt;</span> <span class="token function">btoa</span><span class="token punctuation">(</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>Funkcja <code>btoa</code> koduje ciąg znaków jako base64. Sama w sobie trwa średnio 0,25ms...czyli ułamek milisekund. Ale gdy mocniej się zastanowimy - a na co komu ten hasz? a na co komu to potrzebne?</p><p><img src="https://media.giphy.com/media/s239QJIh56sRW/giphy.gif" alt="Ale po co?"/></p><p>No właśnie! Jeśli meta-folder-id to tylko base64 ze ścieżki, która de facto zawierała też w sobie <code>id</code> źródła, więc była unikatowa względem całej listy, to po co to w ogóle ten cały hash?</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">- id: createUniqueIdForLocation(path), + id: path, name: getLocationName(path),</code></pre></div><p>Ten jeden diff sprawił, że dla 1M elementów zszedłem z 59s na 35s, co dało ~40% zysku 🤯</p><p>Czyli teraz klient nie czekał już 90s a 66s - łącznie z pobieraniem i parsowaniem danych! Biorąc pod uwagę, że wymagania mówiły o budowaniu drzewka w czasie mniejszym niż 60s, to chyba Product Owner oraz klienci powinni być zadowoleni 😅</p><h2>Dalsze kroki</h2><p>Oczywiście nie spoczywamy na laurach. Blokowanie użytkownika na 60s nadal jest kiepskim pomysłem, dlatego dalej myślimy o ulepszeniu implementacji. Być może wyrzucimy to w końcu do web-workera. Kto wie? Może uda mi się dzięki temu zebrać materiał na następnego posta 😉.</p><h2>Podsumowanie</h2><p>Lekcja pierwsza - zamiast gdybać i sypać pomysłami z kapelusza o web-workerach, lepiej odpalić Profiler.</p><p>Lekcja druga - jeśli operujesz w dużej skali, iterujesz po dużym zbiorze danych, to optymalizacje na poziomie <code>ms</code> dla jednej iteracji potrafią czynić cuda 🚀</p><p>Lekcja trzecia - do reduxa wrzucaj dopiero wtedy, kiedy jesteś gotów 💪</p><p>Lekcja czwarta - jeśli nie ma potrzeby, to nie komplikuj sytuacji 😉 (patrz ID &amp; btoa).</p><p>Mam nadzieję, że dzięki tej historii sięgniecie wcześniej do Profilera i uda się Wam poprawić wydajność nie jednej aplikacji.</p></content:encoded>
</item>
<item>
<title>
<![CDATA[ 10 golden advices for junior developer ]]>
</title>
<description>
<![CDATA[ This article has not been translated yet. Here is a Polish version. Stay tuned Czy ten tytuł to clickbait? Oczywiście, że tak. Nie ma na świecie dwóch takich samych osób. To co sprawdziło się u mnie… ]]>
</description>
<link>https://przemuh.dev/en/blog/10-golden-advices-for-junior-developer</link>
<guid isPermaLink="false">https://przemuh.dev/en/blog/10-golden-advices-for-junior-developer</guid>
<pubDate>Tue, 22 Sep 2020 00:00:00 GMT</pubDate>
<content:encoded><p><em>This article has not been translated yet. Here is a Polish version. Stay tuned</em></p><p>Czy ten tytuł to clickbait? Oczywiście, że tak. Nie ma na świecie dwóch takich samych osób. To co sprawdziło się u mnie niekoniecznie musi się sprawdzić u Ciebie. Tak prawdę mówiąc, to tytuł tego posta powinien brzmieć &quot;Co chciałbym przekazać <strong>sobie</strong>, gdybym dzisiaj miał zaczynać jako junior developer&quot;. Ale uznałem, że to trochę zbyt długie. Kto wie, może niektóre z poniższych wskazówek przydadzą się Tobie. Czy niektóre z tych rad są kontrowesyjne? Pewnie tak. Ale wszystkie z nich są szczere, i właśnie takie chciałbym usłyszeć gdybym wchodził jeszcze raz w świat IT. Traktruj ten wpis, raczej jako taki list Przemka do Przemka :)</p><p><strong>TL;RD</strong></p><ol><li>nie buduj portfolio</li><li>załóż konto na Twitterze</li><li>uważaj na celebrytów-IT</li><li>nie kupuj książek</li><li>eksperymentuj, baw się kodem</li><li>naucz się świadomie zarządzać czasem</li><li>zainwestuj w umiejętności miękkie</li><li>znajdź mentora</li><li>kup notatnik</li><li>doszlifuj anglielski</li></ol><h2>Nie buduj portfolio</h2><p>Kiedy zaczynałem szukać swojej pierwszej pracy jako programista (2012 r.) nie wiedziałem, że wyląduję na frontendzie. Nie miałem konta na githubie. Nie miałem portfolio. I gdybym dzisiaj miał szukać pracy, to też bym takiego portfolio nie budował. A już na pewno nie wrzucałbym tam aplikacji typu ToDo List, albo Weather App. Nie zrozum mnie źle, pisanie takich aplikacji jest jak najbardziej ok. Dzięki temu uczysz się jak składać działającą całość. Ale wrzucanie tego do portfolio jak dla mnie nie ma sensu.</p><p>No dobra...ale co zamiast portfolio?</p><p>Odpowiedź jest prosta - Open Source. Na githubie jest mnóstwo bibliotek, do których możesz kontrybuować. Wybierz sobie swoją, przeczytaj dokumentację, spróbuj zbudować lokalnie, zobacz jak wygląda lista Issues - z czym ludzie mają problemy. Dzięki temu nauczysz się o wiele więcej niż przy ToDo czy Weather App. Czytanie cudzego kodu to jedna z najważniejszych umiejętności w byciu programistą. Mówiąc o kontrybucji do Open Source, nie mam na myśli np. nowych ficzerów Reacta (chociaż do odważnych świat należy). Czasami jedna linijka kodu potrafi rozwiązać czyjś problem. Ba! Nie musisz wcale kodować. Pisanie dokumentacji to też cegiełka do Open Source. Pierwszy commit Kent C Dodds&#x27;a w ramach Open Source to była literówka. Nie wierzysz? - <a href="https://kentcdodds.com/blog/how-getting-into-open-source-has-been-awesome-for-me" target="_blank" rel="nofollow noopener noreferrer">zobacz ten wpis</a>.</p><p>Podsumowując - jedno Twoje zdanie podczas rekrutacji - &quot;aktywnie udzielam się w świecie Open Source&quot; znaczy więcej niż portfolio z fajerwerkami.</p><h2>Załóż konto na Twitterze, ale ...</h2><p>Znowu - kiedy zaczynałem szukać pierwszej pracy, jedynym portalem typu social media był dla mnie Facebook. Dopiero po czasie zobaczyłem ile mnie omija. Jeśli chcesz być na bieżąco z informacjami dot. technologii to Twitter jest chyba najlepszą opcją. Facebook = rodzina i przyjaciele, sprawy for fun, śmieszki, heheszki, memy, zdjęcia. Twitter = nowinki technologiczne, ciekawi ludzie z branży, motywacja, inspiracje. Dlatego, jeśli jeszcze nie masz konta na Twitterze, to czym prędzej je zakładaj. Poszukaj kilku osób z branży, a potem poszerzaj listę obserwowanych. Nie bój się również usuwać ludzi z listy. To nie są Twoi przyjaciele - nikt się nie obrazi :) Jeśli nie podobają Ci się treści, jakie wrzuca obserwowana przez Ciebie osoba - po prostu przestań ją obserwować, albo wycisz jej tweety.</p><p>Z durgiej strony - nie obrażaj się, jak Twój &quot;idol&quot; wrzuci od czasu do czasu posta niekoniecznie związanego z programowaniem. Koniec końców jest to portal typu social media. Zrzut ekranu ze Spotify jeszcze nikomu krzywdy nie zrobił ;)</p><p>No ale, co z tym &quot;ale&quot;?</p><h2>Uważaj na celebrytów-IT</h2><p><em>pfff</em> &quot;że co!?&quot;. Wierz mi, albo nie, &quot;kiedyś to było&quot;. Może inaczej - kiedyś to NIE - było tylu blogów, tylu informacji, tylu kursów, tylu &quot;mentorów&quot;. Z czasem ludzie podchwycili temat pt. &quot;praca w IT = dużo hajsu&quot;. Zaczął się napływ ludzi do branży, bo to przecież &quot;łatwe pieniądze&quot;. Siedzi się tylko i klepie w klawisze. Jak grzyby po deszczu zaczęły wyskakiwać kolejne szkoły programowania, bootcampy, kursy, mentorzy. Zrobił się z tego niezły biznes. Przecież każdy chce zarobić - co nie? - co w tym złego?</p><p>Nic. Jeśli masz pieniądze to je wydajesz jak chcesz. Gdybym tylko miał sobie dawać w tej kwesti radę, to powiedziałbym &quot;uważaj na celebrytów w IT&quot;. Trochę takich ludzi szufladkuję, sorry. Naczytali się Aniserowicza i Szafrańskiego, jak to można zarabiać na blogu, kursach itp. Sami ledwo co skończyli bootcamp, liznęli pierwszej pracy a już wydają swój autorski kurs - &quot;Programowanie w HTML dla zaawansowanych&quot;. Brzmi jak dowcip? Niestety. Brzmi jak zazdrość - kto wie - być może. Niestety nikt nie uczy tego, jak odfiltrować dobry content od tego skopiowanego i nastawionego na szybki zysk. Czasami nie jesteśmy w stanie zweryfikować doświadczenia danej osoby. Kto wie - może naczytał się pierdół o żabach i teraz na siłę próbuje zainteresować tym innych :) Sam musisz sobie wyrobić czujnik na takie osoby - Przemku.</p><p>Dobra wiadomość jest taka, że tych dobrych &quot;dusz&quot; jest więcej. A takie celebryto-IT-pijawki zdarzają się sporadycznie.</p><p>PS. Nie zrozum mnie źle. Dzielenie się wiedzą na blogu, vlogu (whatever) nawet jeśli dopiero co nauczyłeś się &quot;czegoś&quot; jest SUPER! Ale natychmiastowa próba zarabiania na tym - już nie - przynajmniej nie dla mnie. Gdy płacę za kurs, to chcę mieć pewność, że dana osoba &quot;zęby na tym zjadła&quot;, a nie naczytała się pierdół o żabach ;)</p><h2>Nie kupuj książek</h2><p>A przynajmniej tych o technologiach. A już na pewno nie kupuj ich z myślą &quot;kiedyś przeczytam&quot;. Książki o technologiach szybko się starzeją. Zwłaszcza te o technologiach frontendowych, o frameworkach itp. Jeśli chcesz mieć fajną podstawkę pod monitor - spoko, your choice :) Mam takie dwie cegły pt. JAVA ^^ #naPóźniej.</p><p>Zamiast tego, skup się na książkach ponadczasowych. &quot;Clean Code&quot;, &quot;Clean Coder&quot;, &quot;Pragmatyczny Programista&quot;, &quot;Zawód Programista&quot;, &quot;Refaktoryzacja&quot; ... to są książki, które się nie starzeją. Po takie książki warto sięgać kilkukrotnie w swojej karierze. Za każdym razem wyciągniesz z niej coś innego, będziesz miał inny punkt widzenia, inny poziom doświadczenia.</p><p>I żeby było jasne - nie ma nic złego w książkach o samych technologiach/frameworkach - o ile kupisz aktualną wersję i przeczytasz ją zaraz po zakupie. W innym przypadku - &quot;daj se siana&quot; ;)</p><p>PS. Jeśli możesz - czytaj w oryginale, inaczej mówiąc - uważaj na tłumaczenia. Wiem, że oryginały są znacznie droższe w porównaniu z wydanymi nad Wisłą &quot;tłumaczeniami&quot; - ale warto. Obserwuj takie strony jak <a href="https://www.humblebundle.com/" target="_blank" rel="nofollow noopener noreferrer">HumbleBundle</a>, tam często pojawiają się &quot;paczki&quot; książek np. z wydawnictwa <a href="https://www.oreilly.com/" target="_blank" rel="nofollow noopener noreferrer">O&#x27;Reilly Media</a> i można je dostać za &quot;śmieszne&quot; pieniądze.</p><h2>Eksperymentuj, baw się kodem</h2><p>Często na grupach dla początkujących czytam: &quot;najpierw skup się na podstawach, dopiero potem zajmij się Reaktem&quot;. Ding-dong - Bullshit detector - Ding Dong. Owszem - podstawy pt. zmienne, pętle, funkcje wypadałoby ogarnąć przed frameworkiem. Ale jak już łykniesz podstawowej składni to śmiało wypływaj na głębie. Podczas nauki samego Reakta otrzesz się o funkcje wyższego rzędu, kompozycje, destrukturyzację i to w takiej praktycznej formie. Nie ma sensu czekać!</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">It&#x27;s fine to learn React while you&#x27;re learning JavaScript. Frameworks can teach you closures, higher order functions, ternaries, etc...</p>— Eric Elliott (@_ericelliott) <a href="https://twitter.com/_ericelliott/status/1269115558479495168?ref_src=twsrc%5Etfw">June 6, 2020</a></blockquote><script async="" src="https://platform.twitter.com/widgets.js" charSet="utf-8"></script><p>Inny wymiar tej rady mówi o tym, że programowanie powinno sprawiać Ci frajdę. Nie bój się napisać &quot;brudnego kodu&quot;, eksperymentuj, baw się. Wykorzystaj różne podejścia. Sprawdź w czym najlepiej się czujesz.</p><p>W programowaniu chodzi o rozwiązywanie problemów. A najpiękniejsze w tym wszystkim jest to, że wiele problemów można rozwiązać na wiele, wiele, wiele różnych sposobów.</p><p>I na koniec - jeśli masz już za sobą ciężki bój przez te &quot;podstawy&quot;, a pisanie każdej kolejnej linijki sprawia Ci ból - to zastanów się czy to na pewno dla Ciebie. Praca programisty nie jest usłana różami, czasami trzeba zanurkować w niemałe szambo i nikt Cię za to po plecach nie poklepie. Z drugiej strony - nie samymi programistami IT stoi. Do branży można wejść na różne sposoby ;)</p><h2>Naucz się świadomie zarządzać czasem</h2><p>Z niecierpliwością czekam, aż mój kolega Radomir, zbierze się w sobie i wygłosi prezentację na temat zarządzania czasem. Sprzedał mi ten temat kiedyś przy jakiejś kawie. Główny przekaz jest mniej więcej taki - jeśli jesteś na studiach to nawet nie wiesz ile masz wolnego czasu. Dopiero (o zgrozo) po czasie dochodzi do nas, ile cennych minut przepaliliśmy. I żebyśmy się dobrze zrozumieli - nie ma nic złego w naparzaniu po nocach w Counter Strike&#x27;a (gra się jeszcze w to?). Nie ma nic złego w imprezowaniu. Jest taki okres w życiu człowieka - studia - gdzie dopiero poznaje się &quot;co to życie&quot;. Korzystaj póki możesz :) Ale pamiętaj - już nigdy nie będziesz miał tyle czasu co teraz. Każdy z nas ma tyle samo czasu. Doba ma 24 godziny. Różnimy się tym, jak ten czas wykorzystujemy. Naucz się swoich nawyków. Swojego organizmu. Sprawdź kiedy jesteś najbardziej produktywny, kiedy najlepiej się uczysz. Wykorzystuj to. Naucz się świadomie zarządzać czasem.</p><p>Ostatnio na Twitterze napisałem, że praca programisty to nieustanna nauka. Tak jest. Na tę naukę też trzeba umieć znaleźć czas. Chcesz prowadzić bloga? Sprawdź ile czasu zajmuje napisanie posta. Kanał na Youtube? Kurs gita, czy babela nagrywam już chyba od lutego :) To nie jest proste. Na wszystko trzeba znaleźć ten cholerny czas. Dlatego Przemku - naucz się świadomie zarządzać czasem.</p><h2>Zainwesuj w umiejętności miękkie</h2><p>Do pierwszej pracy dostałem się na staż. To nawet nie było stanowisko juniorskie. Kiedyś staż kojarzył mi się z parzeniem kawy i wpinaniu kartek do segregatora (tak tak, i jeszcze za to Unia płaciła...ale ciiiiii 🙊). Ale w Samsungu było inaczej. Konkretny projekt, konkretni ludzie, ogrom wiedzy. Dacie wiarę, że startowałem do zespołu C++ (myślałem, że jak kodowałem w tym języku na studiach to się uda) a dostałem się do zespołu SmartTV, w którym pisaliśmy we frontendowych technologiach? W życiu bym nie powiedział. Dla mnie JS kojarzył się tylko z jQuery i śnieżynkami na stronach. No ale nie o tym chciałem pisać.</p><p>Byłem zielony - to fakt. Szybko musiałem nadrobić wiedzę. Udało mi się to dzięki wspaniałym ludziom, z którymi miałem okazję pracować - ale o tym będzie następna rada. Pomimo braku w wielu kwestiach techniczncyh - dosyć szybko awansowałem. Dostałem też możliwość poprowadzenia małego zespołu. Mój szef coś we mnie dostrzegł. Od liceum bardzo lubiłem nawiązywać nowe kontakty. Uważałem się za duszę towarzystwa. Tu coś zagrać na gitarce, tu zagadać, tu się pośmiać. Spotkałem świetnych ludzi na swojej drodze. Byłem szczery, życzliwy, pracowity (teraz też jestem, żeby nie było 😜). Z perspektywy czasu widzę, że to właśnie relacje jakie budowałem z ludźmi pozwoliły mi tak szybko awansować, tak szybko stanąć na czele zespołu.</p><p>Bardzo często nie doceniamy umiejętności miękkich w IT (chociaż zauważam zmieniający się trend). Zdradzę Ci teraz pewien sekret - Przemku - pisanie kodu to nie wszystko. Ba! Umiejętna komunikacja i zdolność do budowania relacji znaczą o wiele więcej niż klepanie kodu.</p><p>Dlatego - zainwestuj w umiejętności miękkie. Sprawdź jak dobrze się komunikować - bo to wcale nie jest takie łatwe.</p><h2>Znajdź mentora</h2><p>W poprzednim punkcie pisałem o świetnych ludziach, z którymi miałem przyjemność współpracować. Szymon K., Grzegorz D., Bartek K., Maciej W. (trochę jak z kryminału :D...no ale nie wiem czy chcieliby się znaleźć tu z nazwiska więc dla pewności zostawiam tak :))...wiele by wymieniać. Wszysktich ich łączy jedno - dążyłem do tego, by być na zbliżonym do nich poziomie. Nie porównywałem się, bo to nie ma sensu. Każdy ma swoją drogę, swoje przeżycia, doświadczenia. Ale możemy się inspirować, czerpać wiedzę, pytać o przeżycia i bazować na doświadzczeniu innych. Dzięki temu zwiększałem prędkość nauki i wchodzenia na wyższy level. Dzisiaj nie wyobrażam sobie pracy w środowisku, w którym nie mogę uczyć się od lepszych od siebie. Zawsze szukam takiej osoby i staram się inspirować.</p><p>PS. Pamiętaj, że to może działać w dwie strony. U kogoś widzisz rzecz, której Tobie brakuje...za to ta druga strona widzi w Tobie rzeczy, które ją inspirują do działania. Dzięki temu wzajemnie się napędzacie.</p><h2>Kup notatnik</h2><p>Wszyscy mówią - załóż bloga, załóż kanał na YT. A ja mówię - kup notatnik. Taki zwykły zeszycik A5 może być. Zapisuj tam skrzętnie to, czego się nauczyłeś. Czytając książki, bądź oglądając kursy - rób notatki. Spisuj swoje osiągnięcia. Zapisuj przemyślenia. Rysuj mapy myśli. To pomaga - serio. Pomaga - ale z czasem. Np. podczas rozmowy o podwyżkę. Takie rozmowy często odbywają się raz do roku. Czy jesteś w stanie z pamięci wyrecytować wszystkie swoje osiągnięcia. Podpowiem Ci - NIE!. Czasami robimy małą rzecz dla siebie, ale wielką dla projektu. Zapisując swoje wszystkie sukcesy i porażki jesteś w stanie popłynąć na takiej ocenie rocznej i wywalczyć upragnioną podwyżkę. Ale nie rób notatek dla pieniędzy :) rób je dla siebie i dla swojego mózgu, który nie zawsze jest w stanie wszystko spamiętać ;)</p><p>PS. Bloga też sobie załóż jak chcesz - dziel się tam swoją nowo-zdobytą wiedzą i doświadczeniem. Tylko proszę - nie nastawiaj się od początku na &quot;zarabianie z bloga&quot;. Nie każ mi akceptować powiadomień i zapisywać się do newslettera :) Na początku bloga prowadź go dla siebie. Sława i splendor przyjdą z czasem....albo i nie :P</p><h2>Doszlifuj angielski</h2><p>W liceum nie byłem jakoś specjalnie zainteresowany angielskim. Tu trójeczka, tam czwóreczka, tu coś ściągnąć, tam zagadać i &quot;jakoś to będzie&quot;. Szybko tego pożałowałem. I choć nie miałem problemu w zrozumieniu słowa pisanego (dokumentacja, książki, maile) to szybko zarobiłem &quot;plaskacza&quot; w momencie, kiedy miałem poprowadzić spotkanie czy prezentację po angielsku. Mówi się, że podstawowym językiem programisty jest język angielski - to prawda. I chociaż coraz więcej publikacji powstaje w języku polskim, tłumaczone są dokumentacje (patrz React) to nadal - posługiwanie się językiem angielskim to podstawa. Bardzo dużo dają tzw. English-Days. W Suncrapers, w którym miałem okazję pracować, był taki jeden dzień - chyba czwartek z tego co pamiętam. W czwartki rozmawiało się tylko po angielsku. To nic, że w projekcie sami polacy. Trzeba było tłumaczyć i szprechać po inglishu. Wiadomo - dla ludzi, którzy dużo się oczytali, ale mało mówili to był problem. Wytłumaczenie prostej rzeczy zajmowało x2, x3 albo x4 tyle co po polsku. Z drugiej strony dzięki takim zabiegom później było łatwiej :)</p><p>Dzisiaj nie mam już problemu z posługiwaniem się językiem angielskim. Cały czas pracuję nad wzbogacaniem słownictwa (aktualnie trenuję z dziećmi Ba-Ba-Black Sheep ;))</p><p>Dlatego Przemku, jeśli to czytasz i chcesz wejść do IT - to wiedz, że bez angielskiego - ani rusz!</p><h2>Podsumowanie</h2><p>I to by było na tyle. Takie rady dałbym <strong>sobie</strong> dzisiaj, gdybym miał zaczynać jeszcze raz w IT. Banalne? Kontrowesyjne? Nudne? Dajcie znać w komentarzach ;)</p></content:encoded>
</item>
<item>
<title>
<![CDATA[ Watch out for fixtures in cypress.io ]]>
</title>
<description>
<![CDATA[ Today I would like to tell you a story about a bug that cost me two days of searching and debugging sessions. It turned out a trivial thing, and with a better error message, it could have taken… ]]>
</description>
<link>https://przemuh.dev/en/blog/watch-out-for-fixtures-in-cypress</link>
<guid isPermaLink="false">https://przemuh.dev/en/blog/watch-out-for-fixtures-in-cypress</guid>
<pubDate>Fri, 26 Jun 2020 00:00:00 GMT</pubDate>
<content:encoded><p>Today I would like to tell you a story about a bug that cost me two days of searching and debugging sessions. It turned out a trivial thing, and with a better error message, it could have taken seconds instead of days. Let&#x27;s go!</p><h2>Hey Przemek! Could you help me?</h2><p>A few days ago, I noticed that our VRT (Visual Regression Tests) suite started to fail for one case. I&#x27;ve asked my colleague, Monica, to check it. She accepted the challenge. After a long day of searching the root cause, she told me that she doesn&#x27;t have any idea why the test is failing. On the local machine, it has been passing all the time, but on our GitlabCI, we got an error. Weird thing, isn&#x27;t it? Monica was resigned and asked me for help. After two days of trying, committing, pushing, waiting, we&#x27;ve finally found it.</p><h2>Fake server</h2><p>We use a lot of tools in our tests. For unit testing, we use <a href="https://jestjs.io/" target="_blank" rel="nofollow noopener noreferrer">jest</a>. In E2E, we use <a href="https://docs.pytest.org/en/stable/" target="_blank" rel="nofollow noopener noreferrer">py.test</a> with webDriver bindings. We also have UI tests that check our app on a higher level (interactions between components, pages, or views). Recently we introduced another test suite - VRT (Visual Regression Tests). The last two (UI and VRT) are based on <a href="https://www.cypress.io/" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a>. It is an excellent tool for writing tests - from unit to full E2E.</p><p>Backend in our app is very complicated, and it is tough to setup a local environment. Because of that, for UI and VRT tests, we use a killer feature from cypress.io - network stubbing. Cypress can plug in between our app and network request giving us a possibility to decide about the response from API endpoint.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&quot;test with network stubbing&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token comment">// First, we need to start fake server</span> cy<span class="token punctuation">.</span><span class="token function">server</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Next, declare the route that we want to stub</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> value<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>More info about stub responses can be found in <a href="https://docs.cypress.io/guides/guides/network-requests.html#Stub-Responses" target="_blank" rel="nofollow noopener noreferrer">official Cypress documentation</a>.</p><h2>Fixtures</h2><p>Fixtures are another feature from <a href="https://www.cypress.io/" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a> that we use a lot, especially in our VRT suite. A fixture is a simple file that holds the data. We can reuse this file in many places. It helps us in organizing tests and managing the common responses from stubbed network requests. To load a fixture, we use a <code>cy.fixture</code> command. It expects a path to the file that we want to load. The path should be relative to a folder specified to hold fixtures (<code>cypress/fixtures</code> by default). Let&#x27;s assume that we have the following file structure:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">- fixtures - myFixture.json - someSubFolder - mySecondFixture.json</code></pre></div><p>And now let&#x27;s look at code which loads fixtures:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&quot;test with fixtures&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token comment">// We don&#x27;t need to specify the file extension</span> <span class="token comment">// Cypress will try to figure it out</span> cy<span class="token punctuation">.</span><span class="token function">fixture</span><span class="token punctuation">(</span><span class="token string">&quot;myFixture&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">data</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token comment">// Here we can read the data</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// We can save the fixture as an alias ...</span> cy<span class="token punctuation">.</span><span class="token function">fixture</span><span class="token punctuation">(</span><span class="token string">&quot;someSubFolder/mySecondFixture&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">as</span><span class="token punctuation">(</span><span class="token string">&quot;myAlias&quot;</span><span class="token punctuation">)</span> <span class="token comment">// ...and then use the alias in stub of response</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;@myAlias&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>Authors of Cypress took care of reducing a boilerplate needed to use a fixture in stubbing network requests 🔥🔥🔥. The <code>cy.route</code> command can take a shortcut to fixture as a response argument:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/path&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fixture:myFixture&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fx:someSubFolder/mySecondFixture&quot;</span><span class="token punctuation">)</span></code></pre></div><p>In this way, we stubbed a network request with data kept in reusable fixture files. Great job!</p><h2>Where is the hero of the story?</h2><p>Ok, but where did our bug go?</p><p>I&#x27;ve created a simple app to visualize the issue. In the beginning, the app displays the <code>Loading…</code> message, then makes a request and replaces the text with a downloaded response.</p><p>Fetching the data in old, good XHR way 😎</p><div class="gatsby-highlight" data-language="html"><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>main<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Loading...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript"> <span class="token keyword">const</span> mainEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">&quot;#main&quot;</span><span class="token punctuation">)</span> <span class="token keyword">const</span> req <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XMLHttpRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span> req<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token string">&quot;GET&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> req<span class="token punctuation">.</span><span class="token function-variable function">onreadystatechange</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>req<span class="token punctuation">.</span>readyState <span class="token operator">==</span> <span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> msg <span class="token operator">=</span> req<span class="token punctuation">.</span>status <span class="token operator">==</span> <span class="token number">200</span> <span class="token operator">?</span> req<span class="token punctuation">.</span>responseText <span class="token operator">:</span> <span class="token string">&quot;Error&quot;</span> mainEl<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> msg <span class="token punctuation">}</span> <span class="token punctuation">}</span> req<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">&gt;</span></span></code></pre></div><p>I&#x27;ve also written a test:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">&quot;Simple fixture test&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&quot;displays response&quot;</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cy<span class="token punctuation">.</span><span class="token function">server</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fixture:examplefixture&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;#main&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;Hello&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>And created a fixture file <code>fixtures/exampleFixture.json</code>:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Hello</code></pre></div><p>Have you noticed a bug yet?</p><p>In my case, the screenshot from the failed test was very helpful. Cypress takes them by default for failing tests, which is neat 🔥!</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/405343e368f5fd3d144f7527bee68ba2/21b4d/screenshot.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:56.25%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAACN0lEQVQoz22SXU/TYBiG+VXCgMFW1nbA9A8YQYmRA6Iy2JgmfhxwQgiHgBxs3UKi8Zd4akKCUT7GBDrGhHVdP1YYePm2AhtokytP3pMrz93n7jIaLsWjU/YrVQ4Eh9VTyhWdsl4im82Sy+VIJpMMRCJIsooUkwNiiooSj9MXHuT5i5fk83mWl1foatVN7I1vuJtfaW5+wdvaoFUqYR78JKtpaIJUKoUsywyPJBgRjI7eJ5F4IGaCqBRjZmaWQqHA6uoqXWcNB2Nnn3pRxzo8wT6u4wiMykmwnS9Mp9MoikJMbBjq7Q/oCfUxFFOIRIdEgpkOoWFS/17E3NqjUdzH1o9xBIZeIesLcxpzc3NIksTY+BO0wjpafp2Pnz4zMfGUvv6B2xteisiXO0UutnfF3KX1Yxt2i2LL6o0wk8kQDofJvHpN5/f23XvudYeYTaXbwgvLxiz/wjyu0bjCrlmYNYOcOIof+a7w95XwzX+FbhPXdjFsD8NyqVsODUdMoy28jvxo7DE5rRDwQfBwfIL+fyK7Lpeug930sGxbyGws174lbB9Fobunl+5QLz1ixq6OMt15lJaIbOhlatUqXsPiXEjPHQfz5DToYacwPjwiqjN6g/8ejESFMNkWepZFvXwk/l2VM0NURohcgb5XYm1tLajOtVCNDyP7hVbjAf47EE5PB8VeWRHFdkTkpufhCpymi/8+a7U41HUWFhZYXFxkamoKVVVvCWXlrzASlXg2OcnS0hLz8/P8AXOxcxI9R2CUAAAAAElFTkSuQmCC&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Screenshot from failed test" title="Screenshot from failed test" src="/static/405343e368f5fd3d144f7527bee68ba2/105d8/screenshot.png" srcSet="/static/405343e368f5fd3d144f7527bee68ba2/3cf3e/screenshot.png 293w,/static/405343e368f5fd3d144f7527bee68ba2/78a22/screenshot.png 585w,/static/405343e368f5fd3d144f7527bee68ba2/105d8/screenshot.png 1170w,/static/405343e368f5fd3d144f7527bee68ba2/21b4d/screenshot.png 1280w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Screenshot from failed test</figcaption> </figure></p><p>And now...Have you noticed a bug yet?</p><p>A message about the status from the stubbed request caught my attention. It was <code>400</code> instead of <code>200</code>. That was a clue.</p><h2>The typo and file systems</h2><p>Our bug, which we&#x27;ve been trying to solve with Monica, was a simple typo. The name of the fixture file was in camelCase, and we tried to load it via shortcut without the same naming convention.</p><p><code>exampleFixture.json</code> vs <code>cy.route(&quot;/api&quot;, &quot;fixture:examplefixture&quot;)</code></p><p>Ok, but why does it work on the local machine and doesn&#x27;t on CI?</p><p>99% of our frontend team works on MacBooks. Our CI runs the tests in the docker container (Linux). You can think - &quot;so what?&quot;. The default file system on Linux is case sensitive. On the other hand, the default file systems on Mac or Windows are not. What does it mean in practice?</p><p>On Linux you can create two files with the &quot;same&quot; name (different letter case):</p><ul><li>myAwesomeFile.js</li><li>myawesomefile.js</li></ul><p>Linux treats them as separate files. Try to do the same on Mac or Windows - you can&#x27;t do it. It has also impact on the way how you load the files, for example in nodejs. On Mac, there is no difference in load file by &quot;myFixture&quot; or &quot;mYFiXtURe&quot; names - the file will be loaded. On Linux, we will get an error - file not found.</p><h2>Let&#x27;s check it</h2><p>If we modify the code of our test in this way:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;fixture:ExAmPlEFiXTuRe&quot;</span><span class="token punctuation">)</span></code></pre></div><p>The test is always green on Mac. On Linux we get a <code>400</code> status for stubbed network request and an error message in console.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/9541d7f7ab7da3ac37cb220e4e54e35a/c211c/stub.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:55.9254327563249%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABBElEQVQoz52R3W6DMAxG8/5XILjlKRBXPEq10VGtLeWvZMQJlPCNqNDSat1glo4sJfaR47AkLXDIKmQZR5Jz5EUFUZ9RFDk45xBCoK5rKKXQti2apnnAnJk7U2dgaVbiY3dEyQm1vECqoam90nUdpuj7/lemYF03SCRB6wt6rYes73ksnDe8iknMiAscNzHSaIdT9Ikk2iPdHnBOigfhUljDvyDftqAovvMeQ50y6BUT3p4siECNAqk5EjSsYdrhKiGNv/OMOZ8LFz+ZxuYbZuJRaoRrZFehETwhRvF/gv0kk1KiqiqEYQjf9xEEwSJM7UthWZbwPA+2bcNxnD9xXReWZeEbYpJEWPqWc/gAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Screenshot with 400 for stubbed request" title="Screenshot with 400 for stubbed request" src="/static/9541d7f7ab7da3ac37cb220e4e54e35a/105d8/stub.png" srcSet="/static/9541d7f7ab7da3ac37cb220e4e54e35a/3cf3e/stub.png 293w,/static/9541d7f7ab7da3ac37cb220e4e54e35a/78a22/stub.png 585w,/static/9541d7f7ab7da3ac37cb220e4e54e35a/105d8/stub.png 1170w,/static/9541d7f7ab7da3ac37cb220e4e54e35a/c211c/stub.png 1502w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Screenshot with 400 for stubbed request</figcaption> </figure></p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">CypressError: The following error originated from your application code, not from Cypress. When Cypress detects uncaught errors originating from your application it will automatically fail the current test. This behavior is configurable, and you can choose to turn this off by listening to the `uncaught:exception` event. https://on.cypress.io/uncaught-exception-from-application </code></pre></div><p>Wait, wait, wait...WAT? The following error originated from your application code, not from Cypress. Are you sure Cypress? 🤔</p><p>Let&#x27;s try to load the fixture without a shortcut:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token comment">// We made a mistake in fixture name</span> cy<span class="token punctuation">.</span><span class="token function">fixture</span><span class="token punctuation">(</span><span class="token string">&quot;examplEFixture&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">as</span><span class="token punctuation">(</span><span class="token string">&quot;response&quot;</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string">&quot;/api/endpoint&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;@response&quot;</span><span class="token punctuation">)</span> <span class="token comment">// With storing fixture in an alias we can use it in our assertions</span> <span class="token comment">// We don&#x27;t need to hardcode the &quot;Hello&quot; string</span> cy<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;@response&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">data</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> cy<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;#main&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>The error message for this code is quite different:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Error: A fixture file could not be found at any of the following paths: &gt; cypress/fixtures/examplEFixture &gt; cypress/fixtures/examplEFixture{{extension}} Cypress looked for these file extensions at the provided path: .json, .js, .coffee, .html, .txt, .csv, .png, .jpg, .jpeg, .gif, .tif, .tiff, .zip Provide a path to an existing fixture file.</code></pre></div><p>And this is the error message that I&#x27;ve been counting on 👏 . We know right the way where we should start looking 😎.</p><h2>Summary</h2><p>There are two takeaways from this story:</p><ul><li>small typo could make you cry for two days of debugging session</li><li>you are as good as the error message from your test runner ;)</li></ul><p>I think that Cypress could return the better message about missing fixtures than <code>CypressError</code>. That&#x27;s why I&#x27;ve created an issue in cypress GitHub repository - <a href="https://github.com/cypress-io/cypress/issues/7818" target="_blank" rel="nofollow noopener noreferrer">here you can check the status</a>.</p><p>Thank you for your attention. I am going to try to solve the issue that I&#x27;ve created 😉. Maybe I will be able to add something to the OpenSource community to make cypress.io even better 😁</p></content:encoded>
</item>
<item>
<title>
<![CDATA[ Stop the time with cy.clock ]]>
</title>
<description>
<![CDATA[ Today I’m going to show you how to stop the time with one command. Unfortunately only in cypress.io tests. If you know how to do it in real life please DM me. It would be a very helpful tip 🙂. Ok… ]]>
</description>
<link>https://przemuh.dev/en/blog/stop-the-time-with-cyclock</link>
<guid isPermaLink="false">https://przemuh.dev/en/blog/stop-the-time-with-cyclock</guid>
<pubDate>Wed, 22 Apr 2020 17:00:00 GMT</pubDate>
<content:encoded><p>Today I’m going to show you how to stop the time with one command. Unfortunately only in <a href="https://cypress.io" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a> tests. If you know how to do it in real life please DM me. It would be a very helpful tip 🙂. Ok, let’s stop joking and get our hands dirty!</p><h2>App description</h2><p>First, we need to have something to test. Our app will be deadly-simple. We want to display the enter time and a counter shows how many seconds we spent in the app.</p><div style="text-align:center;margin:2em 0;border:1px solid;padding:2em"><p>Enter time: <span data-testid="enter-time"></span></p><p>Time on page: <span data-testid="counter">0</span></p></div><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">&quot;react&quot;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>enterDate<span class="token punctuation">,</span> setEnterDate<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>counter<span class="token punctuation">,</span> setCounter<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> React<span class="token punctuation">.</span><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">setEnterDate</span><span class="token punctuation">(</span>Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">const</span> intervalId <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">setCounter</span><span class="token punctuation">(</span><span class="token parameter">prev</span> <span class="token operator">=&gt;</span> prev <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">clearInterval</span><span class="token punctuation">(</span>intervalId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div<span class="token operator">&gt;</span> <span class="token operator">&lt;</span>p<span class="token operator">&gt;</span> Enter time<span class="token operator">:</span> <span class="token operator">&lt;</span>span data<span class="token operator">-</span>testid<span class="token operator">=</span><span class="token string">&quot;enter-time&quot;</span><span class="token operator">&gt;</span><span class="token punctuation">{</span>enterDate<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span> <span class="token operator">&lt;</span>p<span class="token operator">&gt;</span> Time on page<span class="token operator">:</span> <span class="token operator">&lt;</span>span data<span class="token operator">-</span>testid<span class="token operator">=</span><span class="token string">&quot;counter&quot;</span><span class="token operator">&gt;</span><span class="token punctuation">{</span>counter<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span></code></pre></div><p>Ok, we have our app. Now it is time to write some cypress tests.</p><h2>We are testing!</h2><p>In our test scenario we would like to check:</p><ul><li>if the enter time is displayed properly,</li><li>if the counter increases its value after a one-second tick.</li></ul><p>Let&#x27;s try this way:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>The test looks decent but it doesn’t pass 😢</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:569px"> <a class="gatsby-resp-image-link" href="/static/8ea52e51054d035a2d33cf2a73475a17/854dc/datenow-assert-error.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:50.08787346221442%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAABxklEQVQoz42S627TQBCF8/4PUZWW8g4gIQFtkFIgKGnTxEnbOHHs3G92fNnr4Xg3CIk/YOnTWY9nxme00/hyeY35ZoP8dIK1FlpraGOcmrP+C6WUy83zHI2vF1cQUiA7HlGVJYqicFpSCyYYFvzPI0SF3W6HRvPiGnXJ7phin2UopUJBSqXdudIGwlhiPPyBQ535/c687ESHzcu3UEJiO3hG/NBHsdoimyZIw4gaI62ZeM2iBPly42Geoz4vNiio2XbPhlfvYHYH6M4T5MdbiA+foTo9lHctyO8dVO8/Qd7do2reQ7Ta0I8BdOsnFGPqtgXdfoB6HMD2higXKzZ8cwOTnaCCF6ghoVM5GEH2R1CjMeSPrudbB6r/DEP3ZhyRKcwrCWeMxTDMLZYr71DvDxDtLkT3yanqBcB8RdYAxwLHwWINGy9gZ3Oy8MRnTVacoIXTJDo7zAuIIZ2NJ1CzBPI1dIhghKofuLgMJ9B0oGYxVJz4vHDqvomXMW91j5KX6hvydiTHlePQF7OZL/CNVDSD5RSWm2APxz/sjz5OkHJD0tTfsq0XeLmGnEaQtC2jGJaLjnoHuTqQEqgqoPyLymPdWaBkzS92HfDmxAi65wAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Assertion error" title="Assertion error" src="/static/8ea52e51054d035a2d33cf2a73475a17/854dc/datenow-assert-error.png" srcSet="/static/8ea52e51054d035a2d33cf2a73475a17/3cf3e/datenow-assert-error.png 293w,/static/8ea52e51054d035a2d33cf2a73475a17/854dc/datenow-assert-error.png 569w" sizes="(max-width: 569px) 100vw, 569px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Assertion error</figcaption> </figure></p><p>If we would like to show some formatted date (eg. 22-04-2020) instead of a number of milliseconds, then it would not be a problem. But our client wants to display milliseconds and we need to live with this requirement 😉</p><p>The <code>cy.clock</code> command comes with a rescue. It overrides native global functions related to time allowing them to be controlled synchronously via <code>cy.tick()</code> or the yielded <code>clock</code> object. This includes controlling:</p><ul><li><code>setTimeout</code></li><li><code>clearTimeout</code></li><li><code>setInterval</code></li><li><code>clearInterval</code></li><li><code>Date</code></li></ul><p>You can find more info about cy.clock in the cypress.io <a href="https://docs.cypress.io/api/commands/clock.html" target="_blank" rel="nofollow noopener noreferrer">official documentation</a>.</p><p>Now, let’s try to add <code>cy.clock</code> to our test:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>We still get an error. But this time the error message is different.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">expected &lt;span&gt; to have text &#x27;1587547901669&#x27;, but the text was &#x27;0&#x27;</code></pre></div><p>What is going on with that <code>0</code>? Because the time represented in timestamp value is a number of seconds passed from the start of Unix epoch (1st January 1970). We could ask what will happen after the 19th of January 2038 but this is a topic for another blog post 🙂.</p><p>Calling <code>cy.clock</code> without any arguments sets the date in our app to 1st January 1970. We could change it by passing an argument to the <code>cy.clock</code>:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span></code></pre></div><p>Right now, with a little luck, our test will pass. It depends on how fast our computer is 😄. To fix this issue we need to remember that <code>cy.clock</code> overrides the time in our app, not in our tests (command chain). That’s why we need to change <code>Date.now()</code> in our assertion to <code>now</code> value that we’ve created at the beginning of the test.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> now<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div><p>The test is green - always! - success! But there is one little difference in how our app works now. Before using <code>cy.clock</code> our timer has been running. Right now it stops on <code>0</code>. Fortunately, it is expected behavior in our test-case scenario. We&#x27;ve set and stopped the time.</p><p>In order to move the time with some value, we need to call <code>cy.tick</code> command:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> now <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span><span class="token string">&quot;/&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=enter-time]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> now<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=counter]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;0&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">tick</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">&quot;[data-testid=counter]&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">should</span><span class="token punctuation">(</span><span class="token string">&quot;have.text&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;1&quot;</span><span class="token punctuation">)</span></code></pre></div><p>Tada 🎉! We&#x27;ve just wrote the test checking the enter date and the value of the counter.</p><h2>What if we would like to set the date only - without stopping the time? 🤔</h2><p>That’s a great question. Sometimes we would like to override the <code>Date</code> object only, leaving the rest untouched (<code>setTimeout</code>, etc.). In this case, we need to pass a second argument to the <code>cy.clock</code> - an array of timing functions that we want to override.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">cy<span class="token punctuation">.</span><span class="token function">clock</span><span class="token punctuation">(</span>Date<span class="token punctuation">.</span><span class="token constant">UTC</span><span class="token punctuation">(</span><span class="token number">2020</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">22</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">&quot;Date&quot;</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre></div><p>In this example we set the date/time to 22th April 2020 00:00 UTC (yeap - months in <code>Date</code> starts from <code>0</code> that&#x27;s why April = <code>3</code> 🙂). In the same time we don&#x27;t override the <code>setTimeout</code> and the rest time functions.</p><hr/><p>That&#x27;s all for today. I hope that with this knowledge you can go now and stop the time in your tests 😉</p><p>Good luck!</p></content:encoded>
</item>
<item>
<title>
<![CDATA[ 3 Steps to Awesome Test Reports with Cypress ]]>
</title>
<description>
<![CDATA[ In this article, you will learn how to generate informative test reports with Cypress and how to enrich them with some screenshot context. This will help you to fix your potential bugs way faster… ]]>
</description>
<link>https://przemuh.dev/en/blog/3-steps-to-awesome-test-reports-with-cypress</link>
<guid isPermaLink="false">https://przemuh.dev/en/blog/3-steps-to-awesome-test-reports-with-cypress</guid>
<pubDate>Wed, 18 Dec 2019 00:00:00 GMT</pubDate>
<content:encoded><p>In this article, you will learn how to generate informative test reports with Cypress and how to enrich them with some screenshot context. This will help you to fix your potential bugs way faster 😄 All you need is three simple steps.</p><h2>At Egnyte we ❤️ to test</h2><p>Keeping the highest possible quality of the product is one of our top priorities at Egnyte. That&#x27;s why we love to test. But our applications are rather large and relying on manual testing would be exhausting for us. That&#x27;s why test automation and Continuous Integration techniques are our best friends. We write a lot of tests: unit, integration, end-to-end, module, etc. The most important part is that, at the end of the day, if our Jenkins pipeline is green, we are sure that we didn&#x27;t break any parts of the system.</p><p>So where is the problem? Didn&#x27;t you know that tests not always pass? And that&#x27;s fine :) We don&#x27;t need to panic right away. First, let&#x27;s calm down, get into the test report on Jenkins, check what is broken, and fix it. That&#x27;s it. <strong>The problem is that the test report is very often just a plain error message plus a stack trace.</strong> And it&#x27;s enough for unit tests or integration tests for our React components, redux connections, and so on. On the other hand, this is not always helpful for tests that are run in the browser. Let&#x27;s imagine the following result from a test:</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/888b4da9d98b5f76f424ce227440d090/29007/jenkins.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:44.3125%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAABDUlEQVQoz2VQi3KDIBD0/3+x8YGiBwfFaKNoFNJVO7Y2OzfLzTJ7r8Q5R0Te+xDCuuNIlmVZ3xCuSNq27bruuQOGk89knud38aiVWGvR3PsR8H7aMG84HiiHf/+YzloHEniMMUopIkXakW4lOeJ7rVyjXU2bQtyC4YoxXsZGAWZuiIuSb7lOC05LcysQ/JFzJiyYbT/657KGGF+IE5sZnbVmWatKKlFpIS1KZMKkYuOjImbhz/4xTLjjrxmbwMz75Jq5JlNKjWiUrWpGsL279su63rg+/u0LM9RSci6UkDovVVpQJhSSosIiyliHA4cQ1/Vi+zFjbzTHmXHYYZwRj2Eed8aQYChYGEqI//3fzUwBr8ba6BcAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Jenkins console output" title="Jenkins console output" src="/static/888b4da9d98b5f76f424ce227440d090/105d8/jenkins.png" srcSet="/static/888b4da9d98b5f76f424ce227440d090/3cf3e/jenkins.png 293w,/static/888b4da9d98b5f76f424ce227440d090/78a22/jenkins.png 585w,/static/888b4da9d98b5f76f424ce227440d090/105d8/jenkins.png 1170w,/static/888b4da9d98b5f76f424ce227440d090/29007/jenkins.png 1600w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Jenkins console output</figcaption> </figure></p><p>I took this failed test report from our data governance product (<a href="https://www.egnyte.com/protect/content-governance-solution.html" target="_blank" rel="nofollow noopener noreferrer">Egnyte Protect</a>), which is one of our core products. For writing integration-UI tests, we use an awesome tool called <a href="https://cypress.io" target="_blank" rel="nofollow noopener noreferrer">cypress.io</a>. I must admit that Cypress and <a href="https://github.com/testing-library/cypress-testing-library" target="_blank" rel="nofollow noopener noreferrer">cypress-testing-library</a> are doing an excellent job in terms of error messages. Judging by the test report shown above, it is clear that we cannot find an element with matching text. Of course. But what is the visual state of the app? As a developer of Egnyte Protect, I know that this message should appear in a dialog. Has this dialog been opened? Or maybe it is only a typo? So many questions and no answers. If we wanted to check it, we would need to run the test locally once again and see what the visual state of the app is. Only then we would know (spoiler alert) that we have a typo :).</p><p>What if we displayed the visual state of the app right in the Jenkins report?</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/6b320f1a3bdfadd990a910edb663855e/f793b/app.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:54.487179487179496%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABYElEQVQoz42Q2U7CQBSG+yosGr0FSmm8o8x0ppoobVktEhCe0bfQxDsiILK0oJUlYLWrDhaIgYjOfHMuTv4vZ2aoY5Y9YtkwwxwwzGEiQWooFgtEo8E1gUgkTNNB0qTpUDxOMhsoxAsICRh9VywIWEhfpCVR8pFlOUO2JOPzixOIIMJ+0leoFEQreMwBKIri7d19t6c12912pz9Qn7Wh/vI61UZ6uVJJcinA441CAYh8IOkCmMlkHvsD0/Fcx/38sSzbrtfrHJcisY2yLZPLqqpK0p67kj3PW8qWVavX/pAlSdK04cb5rwwgBoDP5XK97pNpWfPF24dp2Y5LjuO4hvFeq+2RSRfAfD7f6zT18Yz80GS2WBimabtktG3tffNyMuSz2WzjoTVfGOPJjKCPp9PJtKmNbhqtavWafPZv8hKEhUJRUUrlS+VKWVMoluSCIpyebYW3ZUKSA7twHNhNfgEqfifvvlhbZQAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Screenshot from the app" title="Screenshot from the app" src="/static/6b320f1a3bdfadd990a910edb663855e/105d8/app.png" srcSet="/static/6b320f1a3bdfadd990a910edb663855e/3cf3e/app.png 293w,/static/6b320f1a3bdfadd990a910edb663855e/78a22/app.png 585w,/static/6b320f1a3bdfadd990a910edb663855e/105d8/app.png 1170w,/static/6b320f1a3bdfadd990a910edb663855e/f793b/app.png 1404w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Screenshot from the app</figcaption> </figure></p><p>Wow! Now we know that the dialog is opened, and that the subheader text is incorrect! We have some valuable context just from reading the test report enriched with a single screenshot.</p><p>So how we could add screenshots to our test reports? Let&#x27;s find out!</p><h2>HTML reports to the rescue!</h2><p>Cypress is based on <a href="https://mochajs.org/" target="_blank" rel="nofollow noopener noreferrer">mocha.js</a>. And this is great because mocha.js is a very mature project with many custom extensions. Test results can be generated within elements called reporters. We can write our custom reporter or use an existing one, for example, <a href="https://www.npmjs.com/package/mochawesome" target="_blank" rel="nofollow noopener noreferrer">mochawesome</a>. As the name suggests, it generates <strong>AWESOME</strong> reports! Badum tsss.</p><p>And now, I would like to show you how to integrate <a href="https://www.npmjs.com/package/mochawesome" target="_blank" rel="nofollow noopener noreferrer">mochawesome</a> with <a href="https://cypress.io" target="_blank" rel="nofollow noopener noreferrer">cypress</a> to generate HTML reports with a screenshot context for failed tests. For the sake of this blog post, I&#x27;ve used an example repo <a href="https://github.com/cypress-io/cypress-example-kitchensink" target="_blank" rel="nofollow noopener noreferrer">cypress-example-kitchensink</a>. We will do it in 3 simple steps. Let&#x27;s get our hands dirty!</p><h2>Step 1 - set up the reporter</h2><p>First, we need to install proper reporters. Yes, that&#x27;s right - plural - reporters. We still want to see test results in the console. Maybe you also want to have a JUnit XML report. We need to have one reporter per expected outcome (console, HTML, XML). In order to set up many reporters, we will use the <a href="https://www.npmjs.com/package/cypress-multi-reporters" target="_blank" rel="nofollow noopener noreferrer">cypress-multi-reporters</a> package. On top of that, we also need <code>mocha</code> and, of course, <code>mochawesome</code>.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> --save-dev mocha cypress-multi-reporters mochawesome</code></pre></div><p>Or if you use <code>yarn</code>:</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token function">add</span> -D mocha cypress-multi-reporters mochawesome</code></pre></div><p>Then, in the <code>cypress.config</code> file, we need to specify which reporter we want to use:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">&quot;reporter&quot;</span><span class="token operator">:</span> <span class="token string">&quot;cypress-multi-reporters&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;reporterOptions&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;configFile&quot;</span><span class="token operator">:</span> <span class="token string">&quot;reporter-config.json&quot;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div><p>The <code>configFile</code> field points to the reporters configuration file. We need to add this file to our repository. For each of the reporters we can specify some options. Let&#x27;s do that for the mochawesome reporter:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">&quot;reporterEnabled&quot;</span><span class="token operator">:</span> <span class="token string">&quot;mochawesome&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;mochawesomeReporterOptions&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">&quot;reportDir&quot;</span><span class="token operator">:</span> <span class="token string">&quot;cypress/results/json&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;overwrite&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;html&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;json&quot;</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div><p>In this fragment of config, we specify an output directory for the results file. We want to collect only the JSON files for each spec file. That&#x27;s why the <code>html</code> flag has been set to false. Because cypress is able to run tests in parallel, we need to set the <code>overwrite</code> flag to <code>false</code>. It means that for each spec file, we will generate a separate file. In our case, these will be JSON files.</p><p>Let&#x27;s try to run our tests via <code>npm run local:run</code> command.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">Running: examples/location.spec.js <span class="token punctuation">(</span><span class="token number">9</span> of <span class="token number">19</span><span class="token punctuation">)</span> Location ✓ cy.hash<span class="token punctuation">(</span><span class="token punctuation">)</span> - get the current URL <span class="token builtin class-name">hash</span> <span class="token punctuation">(</span>169ms<span class="token punctuation">)</span> ✓ cy.location<span class="token punctuation">(</span><span class="token punctuation">)</span> - get window.location <span class="token punctuation">(</span>101ms<span class="token punctuation">)</span> ✓ cy.url<span class="token punctuation">(</span><span class="token punctuation">)</span> - get the current URL <span class="token punctuation">(</span>78ms<span class="token punctuation">)</span> <span class="token number">3</span> passing <span class="token punctuation">(</span>1s<span class="token punctuation">)</span> <span class="token punctuation">[</span>mochawesome<span class="token punctuation">]</span> Report JSON saved to /Users/przemuh/dev/cypress-example-kitchensink/cypress/results/json/mochawesome_008.json</code></pre></div><p>As you can see, after the spec reporter results, we received information that the <code>mochawesome_008.json</code> file has been created. Each of the spec files generated a JSON with results.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:652px"> <a class="gatsby-resp-image-link" href="/static/581d079e46b5dd429410b52138039577/dba9a/list.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:142.0245398773006%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAIAAADuuAg3AAAACXBIWXMAAAsSAAALEgHS3X78AAAB90lEQVQ4y5WU227TQBCGc1PUCiHuUGkpEHsTJz7Eu7PrdcINRa3UCNRKXPEMiEtS1Mu+QHlmfnuSSULSrjsarSaHz/+cvL2XX/+8+nb77sd9/euv+/nw+vruxdXvw/niCT+aLw6uFm9ubntn7uJ9dalmc33xPf18fUpf3urzE9Oc7Ih3/bg8H36a98aDKB2qIh04M5lk40T1RypqvY+f4AiSuD+MPmy6+nimi7SXJKMkGWudkM0MuaqqfF27ylvr+Ky8z/I8jpVSA/EoistSA4aNAFubE1kYeJzUfnDOGYOUJnFLi0VRVJblEjYGcEaEPzfizOCs6xqPCMArZSdG7QfOYi+ML9cwUW4MSdpQ5hhP6ahsuU6p2XuPoCiKXTjP8yVclksYIADmWRnxrjLbVsM4bTatNbDpdLpXeQtG2s4VzWRXxm3jzPNmzk/CxmRaGyJ4Y1DmtNG5MMyjYikeFQzx3lFtwWgY0bJmzlZGhTNcM5Q3YVbmOMuycLdlPXk32BCnadpFWSZlBcbjOsHtkjhZEl4yKAe6LQ2TOk1rzD9DGa+hKHMLAsqr9bT/jYr3LNBtXs+2wGZD5TLhFoQ3DDVrTVwkTi6Ys+hY8/oa2sw8AMv7LO+wiHdSXu32Om2+A8M1o9veFx5Xdl3PZjPcAdw5PEg9bv8A9dAma9wEJYgAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="List of generated results" title="List of generated results" src="/static/581d079e46b5dd429410b52138039577/dba9a/list.png" srcSet="/static/581d079e46b5dd429410b52138039577/3cf3e/list.png 293w,/static/581d079e46b5dd429410b52138039577/78a22/list.png 585w,/static/581d079e46b5dd429410b52138039577/dba9a/list.png 652w" sizes="(max-width: 652px) 100vw, 652px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">List of generated results</figcaption> </figure></p><p>We are ready to go to the next step.</p><h2>Step 2 - generate the report</h2><p>We&#x27;ve collected the test results. Now, we need to merge them into one file and generate an HTML report based on it. We will use the <a href="https://www.npmjs.com/package/mochawesome-merge" target="_blank" rel="nofollow noopener noreferrer">mochawesome-merge</a> tool to merge result files. Let&#x27;s install it.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> i --save-dev mochawesome-merge <span class="token function">yarn</span> <span class="token function">add</span> -D mochawesome-merge</code></pre></div><p>Now, let&#x27;s add an npm script which will be responsible for running the merge tool.</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report:merge&quot;: &quot;mochawesome-merge --reportDir cypress/results/json &gt; cypress/results/mochawesome-bundle.json&quot;</code></pre></div><p>The <code>reportDir</code> flag specifies where we keep results files. The output of the command is passed from stdout to the <code>mochawesome-bundle.json</code>. One caveat here: the result of the merge needs to be put in a different folder than where the single results file is.</p><p>After merging we are ready to generate a final HTML report. In this case, we will use <a href="https://www.npmjs.com/package/mochawesome-report-generator" target="_blank" rel="nofollow noopener noreferrer">mochawesome-report-generator</a>.</p><div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> i --save-dev mochawesome-report-generator <span class="token function">yarn</span> <span class="token function">add</span> -D mochawesome-report-generator</code></pre></div><p>Let&#x27;s create an npm script for that action:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report:generate&quot;: &quot;marge cypress/results/mochawesome-bundle.json -o cypress/reports/html&quot;</code></pre></div><p>Marge is a short form of <strong>M</strong>och<strong>a</strong>wesome<strong>R</strong>eport<strong>GE</strong>nerator in case you&#x27;ve been wondering :)</p><p>Once the script has run, our awesome HTML report should appear in the <code>cypress/results/html</code> folder.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/c44c00ff45ade76717090ba27ef04155/29007/reporter.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:55.6875%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAAA80lEQVQoz5WR227CMAyG+/IDaRcUuoNE+niM0m6Q2M45abd5RWJotEL7ZOUi8qf8dorHl9e1qFdbsdhUD+vNonpaVs/Ln3OiuLkUotyKsq4rIYqcU0ohpxhz0J6+7pFS5H6umGIBgGcUwOHUdvL9qI6oUTtDlqyzbiSOBO/xAmBBFzQ13f7tY8eyQvDehxjiFZyQT7riV0YiCScyZJzxwecppmVNWqJssbOGc9rPGTj2rNzIhiex1g7DcGvylbdWz8l72RAQ76Lv+0k5ODcfG1qOzTKPx7/yj9gKVTfOjIS82Ek5hjD78kG1hjQAnMe+5U/sb8tlYyeUKfRVAAAAAElFTkSuQmCC&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="HTML report view" title="HTML report view" src="/static/c44c00ff45ade76717090ba27ef04155/105d8/reporter.png" srcSet="/static/c44c00ff45ade76717090ba27ef04155/3cf3e/reporter.png 293w,/static/c44c00ff45ade76717090ba27ef04155/78a22/reporter.png 585w,/static/c44c00ff45ade76717090ba27ef04155/105d8/reporter.png 1170w,/static/c44c00ff45ade76717090ba27ef04155/29007/reporter.png 1600w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">HTML report view</figcaption> </figure></p><p>There is one more thing to do. Add a screenshot to tests that have failed.</p><h2>Step 3 - add screenshot context</h2><p>Cypress automatically generates screenshots for failed tests in the <code>cypress/screenshots</code> folder. You can disable this behavior if you want. Screenshots are collected within the following folder structure:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">path-to-the-specfile/spec.file.js/context - describe - describe - testTitle (failed).png</code></pre></div><p>For example, the following test placed in examples/actions.spec.js:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token function">context</span><span class="token punctuation">(</span><span class="token string">&#x27;Actions&#x27;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">context</span><span class="token punctuation">(</span><span class="token string">&quot;nested context&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">&#x27;.type() - type into a DOM element&#x27;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>will generate something like this on fail:</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1056px"> <a class="gatsby-resp-image-link" href="/static/08b26a7260e306a2e5536e34e45fad84/fd84e/folder-structure.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:17.424242424242426%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAIAAAAcOLh5AAAACXBIWXMAAAsSAAALEgHS3X78AAAAVklEQVQI15WOUQrAMAhDe5Y6LLaKrNr7X20ZZfvbx95HEDGJZYwx51xrmRkRHX8orTV3jwikMDN9U2t9ddfc5tPRnPBnpqoipfcuIhi2Auzxmj7gAOYL0ZwcBDCNqUcAAAAASUVORK5CYII=&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="Folder with screenshot from failed test" title="Folder with screenshot from failed test" src="/static/08b26a7260e306a2e5536e34e45fad84/fd84e/folder-structure.png" srcSet="/static/08b26a7260e306a2e5536e34e45fad84/3cf3e/folder-structure.png 293w,/static/08b26a7260e306a2e5536e34e45fad84/78a22/folder-structure.png 585w,/static/08b26a7260e306a2e5536e34e45fad84/fd84e/folder-structure.png 1056w" sizes="(max-width: 1056px) 100vw, 1056px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">Folder with screenshot from failed test</figcaption> </figure></p><p>Ok, so how we can connect these two elements: a screenshot generated by Cypress and a test result generated by a mochawesome reporter?</p><p>First, let&#x27;s copy our generated screenshots to the folder where we keep the HTML reports. In order to do this, we will use an npm script:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report:copyScreenshots&quot;: &quot;cp -r cypress/screenshots cypress/results/html/screenshots&quot;</code></pre></div><p>Next, we will use the cypress/support/index.js file and write some code that will be listening on the <code>test:after:run</code> event.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">Cypress<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">&quot;test:after:run&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">test<span class="token punctuation">,</span> runnable</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>test<span class="token punctuation">.</span>state <span class="token operator">===</span> <span class="token string">&quot;failed&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// do something</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>For adding the screenshot to the test result, we need to use the addContext method from the <code>mochawesome/addContext</code> package. This method takes two arguments: an object with the test, and the context. If the context is a valid URL (could be a local path) to the image, then that image will be displayed. To see more details, visit the <a href="https://www.npmjs.com/package/mochawesome#adding-test-context" target="_blank" rel="nofollow noopener noreferrer">documentation page</a>.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> addContext <span class="token keyword">from</span> <span class="token string">&#x27;mochawesome/addContext&#x27;</span> Cypress<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">&quot;test:after:run&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">test<span class="token punctuation">,</span> runnable</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>test<span class="token punctuation">.</span>state <span class="token operator">===</span> <span class="token string">&quot;failed&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> imageUrl <span class="token operator">=</span> <span class="token string">&quot;?&quot;</span><span class="token punctuation">;</span> <span class="token function">addContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span> test <span class="token punctuation">}</span><span class="token punctuation">,</span> imageUrl<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>Ok - but how to define the <code>imageUrl</code>? This is a time for magic to happen.</p><p><img src="https://media.giphy.com/media/12NUbkX6p4xOO4/giphy.gif" style="width:50%;margin:auto;display:block"/></p><p>Just kidding :) we will use the runnable object. As we saw earlier, Cypress generates the name of the screenshot based on the test suite structure. We need to re-create that.</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">Cypress<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">&#x27;test:after:run&#x27;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">test<span class="token punctuation">,</span> runnable</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>test<span class="token punctuation">.</span>state <span class="token operator">===</span> <span class="token string">&#x27;failed&#x27;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> item <span class="token operator">=</span> runnable <span class="token keyword">const</span> nameParts <span class="token operator">=</span> <span class="token punctuation">[</span>runnable<span class="token punctuation">.</span>title<span class="token punctuation">]</span> <span class="token comment">// Iterate through all parents and grab the titles</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>item<span class="token punctuation">.</span>parent<span class="token punctuation">)</span> <span class="token punctuation">{</span> nameParts<span class="token punctuation">.</span><span class="token function">unshift</span><span class="token punctuation">(</span>item<span class="token punctuation">.</span>parent<span class="token punctuation">.</span>title<span class="token punctuation">)</span> item <span class="token operator">=</span> item<span class="token punctuation">.</span>parent <span class="token punctuation">}</span> <span class="token keyword">const</span> fullTestName <span class="token operator">=</span> nameParts <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">&#x27; -- &#x27;</span><span class="token punctuation">)</span> <span class="token comment">// this is how cypress joins the test title fragments</span> <span class="token keyword">const</span> imageUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">screenshots/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> Cypress<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>name <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>fullTestName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> (failed).png</span><span class="token template-punctuation string">`</span></span> <span class="token function">addContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span> test <span class="token punctuation">}</span><span class="token punctuation">,</span> imageUrl<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div><p>From now on, if our test fails, a context field with a local URL to the image will appear in the JSON results file:</p><div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">&quot;title&quot;</span><span class="token operator">:</span> <span class="token string">&quot;.type() - type into a DOM element&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;fullTitle&quot;</span><span class="token operator">:</span> <span class="token string">&quot;Actions .type() - type into a DOM element&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;timedOut&quot;</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">&quot;duration&quot;</span><span class="token operator">:</span> <span class="token number">10395</span><span class="token punctuation">,</span> <span class="token property">&quot;state&quot;</span><span class="token operator">:</span> <span class="token string">&quot;failed&quot;</span><span class="token punctuation">,</span> <span class="token property">&quot;speed&quot;</span><span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span> <span class="token property">&quot;pass&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;fail&quot;</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token property">&quot;pending&quot;</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token property">&quot;context&quot;</span><span class="token operator">:</span> <span class="token string">&quot;screenshots/examples/actions.spec.js/Actions -- .type() - type into a DOM element (failed).png&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">}</span></code></pre></div><p>What is more, the image itself will be attached to the HTML report.</p><p><figure class="gatsby-resp-image-figure"> <span class="gatsby-resp-image-wrapper" style="position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1170px"> <a class="gatsby-resp-image-link" href="/static/8fd9e50f05c93dd52922645fba1b999e/29007/reporter-error.png" style="display:block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom:55.49999999999999%;position:relative;bottom:0;left:0;background-image:url(&#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAABuklEQVQoz41S21LCMBTs/z+rI6CgUvSZYQb+Qn1BcBS8l0tL0iZtAq1kPUmRQeXBzOxs0p5s9ly8arOJ+tUVjs/PcXBaJ5zisFLBYb2O6kUTJ34LNd9HjeIqdK42fYejxpmLP2o0UGu1zFnrEv6Frz0YA6zXcGyX5eUSeZY5FEqj0LqM+b1srMPalEejPcY5OMEyY3bPEM3nGI2f8Tgab/H2/gEex4iTBGmaOjDGynucm5j+kY72EgrYQiSQiQCLQkyiKSIWkQCJyBhCCmTkWJHb1WoFTWzvCCGIhZFSWl569oPDRlTSPpxN8BEFYAknAYW8yEvkJYqi+CWYGPtgFC3MT8ENC0olzVIqpXYXFdWxdJZDU30tMqW+xUojUpowDLFXUJF9LiRkplzN3t6nmIcLSlnZtCh9SQ9mu4IuZRLc7zCTKUaTKcYBCS0YgsmcBJmrIXXSYUkud1LGRnC/Q81jJA9PEPcjCHKmVwXWnz/Hxtbx34KKWNI4GGpAOJvh+uYWd3cDDIdDDAYD9Pt9BEHgRmenhn8F3Ws0T5oaYptg18vrKzqdDnq9HrrdruN2u+1EbcPs/O0KfgFmmzLlPg5ZHQAAAABJRU5ErkJggg==&#x27;);background-size:cover;display:block"></span> <img class="gatsby-resp-image-image" alt="HTML report with screenshot context" title="HTML report with screenshot context" src="/static/8fd9e50f05c93dd52922645fba1b999e/105d8/reporter-error.png" srcSet="/static/8fd9e50f05c93dd52922645fba1b999e/3cf3e/reporter-error.png 293w,/static/8fd9e50f05c93dd52922645fba1b999e/78a22/reporter-error.png 585w,/static/8fd9e50f05c93dd52922645fba1b999e/105d8/reporter-error.png 1170w,/static/8fd9e50f05c93dd52922645fba1b999e/29007/reporter-error.png 1600w" sizes="(max-width: 1170px) 100vw, 1170px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0" loading="lazy"/> </a> </span> <figcaption class="gatsby-resp-image-figcaption">HTML report with screenshot context</figcaption> </figure></p><p>TADA 🎉 We got it!</p><p>You can check all necessary code changes that we have done in the following pull request: <a href="https://github.com/przemuh/cypress-example-kitchensink/pull/1/files" target="_blank" rel="nofollow noopener noreferrer">https://github.com/przemuh/cypress-example-kitchensink/pull/1/files</a></p><h2>Optional steps</h2><p>You might want to add <code>cypress/results</code> and <code>cypress/reports</code> folders to your <code>.gitignore</code>.</p><p>It would be good to remove screenshots, results, and reports before the next test run. It can be done by a simple npm script. In our example repo, I&#x27;ve added:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;precy:run&quot;: &quot;rm -rf cypress/screenshots cypress/results cypress/reports&quot;</code></pre></div><p>&quot;Pre&quot; means that this script will be run before every <code>cy:run</code>. See <a href="https://docs.npmjs.com/misc/scripts" target="_blank" rel="nofollow noopener noreferrer">npm docs</a> for more details.</p><p>In the example repo, there is a <a href="https://www.npmjs.com/package/npm-run-all" target="_blank" rel="nofollow noopener noreferrer">npm-run-all</a> package installed. We could use it to run in sequence: merge, generate report and copy screenshots scripts in one command:</p><div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">&quot;report&quot;: &quot;run-s report:*&quot;, &quot;report:merge&quot;: &quot;mochawesome-merge --reportDir cypress/results/json &gt; cypress/results/mochawesome-bundle.json&quot;, &quot;report:generate&quot;: &quot;marge cypress/results/mochawesome-bundle.json -o cypress/reports/html&quot;, &quot;report:copyScreenshots&quot;: &quot;cp -r cypress/screenshots cypress/reports/html/screenshots&quot;</code></pre></div><p>There is also one caveat. The file name in most systems is limited to 255 characters. So what will happen when we have a very nested structure of a test suite with long descriptions? It&#x27;s simple - our file name will be truncated. Cypress truncates the full test name to 220 characters. So we could also do the same in our code:</p><div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">MAX_SPEC_NAME_LENGTH</span> <span class="token operator">=</span> <span class="token number">220</span><span class="token punctuation">;</span> <span class="token keyword">const</span> fullTestName <span class="token operator">=</span> nameParts <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>Boolean<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">&quot; -- &quot;</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token constant">MAX_SPEC_NAME_LENGTH</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>But this is an implementation detail. We don&#x27;t know whether Cypress devs are about to change that number. So, a better option would be to read an article from <a href="https://kentcdodds.com/" target="_blank" rel="nofollow noopener noreferrer">Kent C Dodds</a> about <a href="https://kentcdodds.com/blog/avoid-nesting-when-youre-testing" target="_blank" rel="nofollow noopener noreferrer">avoiding nesting when you are testing</a>.</p><h2>Wrap-up time</h2><p>I hope that this article will help you to set up awesome HTML reports in your project. It helps us a lot when it comes to quick investigations of why a given test is failing. Let&#x27;s recap what we did here:</p><ol><li>Install and set up the mochawesome reporter.</li><li>Collect test results and generate an HTML report based on the merged JSON file</li><li>Add screenshot context with an <code>addContext</code> function.</li></ol><p>You can check all code changes here in this <a href="https://github.com/przemuh/cypress-example-kitchensink/pull/1/files" target="_blank" rel="nofollow noopener noreferrer">pull request</a>. And of course, after you generate the HTML report you need to connect it somehow to your Continuous Integration tool. But this is a story for a separate post. :)</p><p>Now…are you ready to create your own Cypress HTML reports?</p></content:encoded>
</item>
</channel>
</rss>