{"@attributes":{"version":"2.0"},"channel":{"title":"DEV Community: Phil","description":"The latest articles on DEV Community by Phil (@philscode).","link":"https:\/\/dev.to\/philscode","image":{"url":"https:\/\/media2.dev.to\/dynamic\/image\/width=90,height=90,fit=cover,gravity=auto,format=auto\/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F848456%2F94f35dc2-0fd4-4b08-a73f-b36f4c936267.png","title":"DEV Community: Phil","link":"https:\/\/dev.to\/philscode"},"language":"en","item":[{"title":"E2E Testing a Video Game","pubDate":"Fri, 03 Jun 2022 21:32:51 +0000","link":"https:\/\/dev.to\/philscode\/e2e-testing-a-video-game-2okh","guid":"https:\/\/dev.to\/philscode\/e2e-testing-a-video-game-2okh","description":"<p>For a better reading experience, you can read this article on my blog at <a href=\"https:\/\/philscode.com\/blog\/e2e-testing-a-video-game\/\">philscode.com<\/a>.<\/p>\n\n<h2>\n  \n  \n  The Scenario \ud83d\uddbc\ufe0f\n<\/h2>\n\n<p>So you've successfully released a dozen popular JavaScript games over the past few years and just as you're about to kick back, enjoy the weekend and let the revenue flow in, a new Tweet appears in your feed.<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--pAuBcLRL--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/9eod9dvhivk00yqnvqw7.png\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--pAuBcLRL--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/9eod9dvhivk00yqnvqw7.png\" alt=\"A fake Tweet that reads, Update your applications or we will remove them from our app store.\" width=\"800\" height=\"479\"><\/a><\/p>\n\n<p>Not great news to hear on a Friday. Okay, I'll just check how many of my games are affected...<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--vL0GEpbH--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/m051igiob8naym7jw6k5.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--vL0GEpbH--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/m051igiob8naym7jw6k5.gif\" alt=\"Long list being revealed.\" width=\"500\" height=\"248\"><\/a><\/p>\n\n<p>\ud83d\ude32\ud83d\ude32\ud83d\ude32\ud83d\ude30\ud83d\ude30<\/p>\n\n<p>No need to panic. I'll just update my packages to the latest version and deploy them.<\/p>\n\n<p>\u270b Wait, just a second!<\/p>\n\n<p>What about all those scenarios in each game? I haven't played these games in years and there are hundreds of test scenarios. I could rely on the Unit Testing and hire QA staff to manually test all my games, though that will cost me all the hard earned cash my games are generating.<\/p>\n\n<p>Manual testing is never good, and absolutely not scalable. There must be a better way to handle this!<\/p>\n\n<h2>\n  \n  \n  The Truth \ud83e\uddd1\u200d\u2696\ufe0f\n<\/h2>\n\n<p>The above scenario may seem far-fetched but a very similar email found its way to the inbox of <a href=\"https:\/\/twitter.com\/protopop\">Protopop Games<\/a> (on a Friday no less!). See the below Tweet.<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--HPTOWWkX--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/v3tfkl0kd0njyk20dles.png\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--HPTOWWkX--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/v3tfkl0kd0njyk20dles.png\" alt=\"A real Tweet from a developer detailing an ask from Apple to update their application or it will be removed.\" width=\"800\" height=\"385\"><\/a><\/p>\n\n<p>As mentioned in the Tweet above, Apple has a new <a href=\"https:\/\/developer.apple.com\/news\/?id=gi6npkmf\">App Store Improvement Plan<\/a> that essentially means they are removing applications that no longer receive updates. For some applications this does indeed make sense however, for games this does not always make sense.<\/p>\n\n<p>At the end of the day, it does not matter what you or I think - if Apple, Google, Steam or any platform want to enforce new rules and make you update all of your applications they can do just that. Our best defence against these scenarios? Robust pipelines that not only Unit test but also E2E test our games.<\/p>\n\n<h2>\n  \n  \n  The Solution \ud83d\udca1\n<\/h2>\n\n<p>To demonstrate this I am going to develop a simple JavaScript based game using <a href=\"http:\/\/phaser.io\/\">Phaser<\/a> and E2E test the game using some advanced techniques of <a href=\"https:\/\/www.cypress.io\/\">Cypress<\/a>. <strong>There will be no arbitrary waiting involved<\/strong> - meaning more reliable, predictable tests.<\/p>\n\n<p>In a nutshell, these E2E tests are going to interact with the game, take a screenshot and compare the image to a good known baseline.<\/p>\n\n<p>For a quick glance, check below to see a few of the tests.<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--91EIJ9Iv--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/u3m6wrg6dsu9c2ufw3fv.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--91EIJ9Iv--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/u3m6wrg6dsu9c2ufw3fv.gif\" alt=\"Automated game tests running.\" width=\"\" height=\"\"><\/a><\/p>\n\n<p>\ud83d\ude0e Pretty cool, right?<\/p>\n\n<p>These tests don't just take full screenshots and compare but rather sections of the Canvas to compare against a known baseline. You definitely don't want to take too many full screenshots as your tests will become very brittle very quickly.<\/p>\n\n<p>Some images are also mocked for specific tests however, it is a good idea to mock all images with a transparent version in order to allow you to test in isolation.<\/p>\n\n<p>For example, the Player and the Zombies both animate which means taking a screenshot over and over would potentially result in a different outcome. This would be way too unreliable and result in flaky testing so instead, the spitesheets are mocked with a solid colour. See the comparison below along with the code to mock the image.<\/p>\n\n<p>Image comparison:<br>\n<a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--aydaGp40--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/xj7k06icfftwzjgyq39k.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--aydaGp40--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/xj7k06icfftwzjgyq39k.gif\" alt=\"A girl running on the spot next to a solid red rectangle.\" width=\"800\" height=\"398\"><\/a><\/p>\n\n<p>Cypress snippet to replace the Player with a custom image:<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nx\">cy<\/span><span class=\"p\">.<\/span><span class=\"nx\">intercept<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">\/assets\/img\/player.png<\/span><span class=\"dl\">\"<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span> <span class=\"na\">fixture<\/span><span class=\"p\">:<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">mock-player.png<\/span><span class=\"dl\">\"<\/span> <span class=\"p\">});<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>Yep, it's that simple!<\/p>\n\n<h2>\n  \n  \n  Conclusion\n<\/h2>\n\n<p><em>In conclusion...<\/em><\/p>\n\n<ul>\n<li>Have <strong>maintained<\/strong> robust pipelines that not only deploy but also rigorously test your applications.<\/li>\n<\/ul>\n\n<p><em>When screenshot testing...<\/em><\/p>\n\n<ul>\n<li>Avoid too many large screenshots.<\/li>\n<li>Test in isolation by mocking images that are not part of the test (using transparent versions).<\/li>\n<li>Mock unpredictable spritesheets with solid references.<\/li>\n<li>Never use explicit waiting but rather poll for a specific outcome.<\/li>\n<\/ul>\n\n<p>Feel free to <a href=\"https:\/\/twitter.com\/intent\/user?screen_name=philscode\">reach out to me on Twitter<\/a> - I look forward to hearing from you! <\/p>\n\n<p>I had a lot of fun putting this together and was able to do so quickly with the help of <a href=\"https:\/\/github.com\/yandeu\/phaser-project-template\">Yannick's boilerplate<\/a>, <a href=\"https:\/\/www.kenney.nl\/\">Kenny's Assets<\/a>, <a href=\"https:\/\/phaser.io\/\">Phaser<\/a>, <a href=\"https:\/\/www.cypress.io\/\">Cypress<\/a> and <a href=\"https:\/\/github.com\/meinaart\/cypress-plugin-snapshots\">cypress-plugin-snapshots<\/a>.<\/p>\n\n<p>If you liked this article check out the in-depth article I wrote on <a href=\"https:\/\/philscode.com\/blog\/cypress-visual-testing-intro\/\">Visual Testing for Software Development<\/a>.<\/p>\n\n<h2>\n  \n  \n  Bonus: Play the Game \ud83c\udfae\n<\/h2>\n\n<p><a href=\"https:\/\/philscode.com\/blog\/e2e-testing-a-video-game\/#bonus-play-the-game-\">Click here to play the final result. Enjoy! \ud83c\udfa2.<\/a><\/p>\n\n<p>As a challenge comment your first high score attempt below!<\/p>\n\n","category":["javascript","testing","gamedev","webdev"]},{"title":"Final Fantasy VIII & TypeScript","pubDate":"Sat, 23 Apr 2022 12:44:04 +0000","link":"https:\/\/dev.to\/philscode\/final-fantasy-viii-typescript-1l7l","guid":"https:\/\/dev.to\/philscode\/final-fantasy-viii-typescript-1l7l","description":"<p>For a better reading experience, you can read this article on my blog at <a href=\"https:\/\/philscode.com\/blog\/ff8-typescript\/\">philscode.com<\/a>.<\/p>\n\n<h2>\n  \n  \n  Overview \ud83d\uddd2\ufe0f\n<\/h2>\n\n<p>Growing up I was an avid fan of the Final Fantasy game series. Particularly Final Fantasy 7 through 10. Each game in the series contains an in-depth story, hours of gameplay, and some mini-games. One mini-game introduced during Final Fantasy 8 was a card game known as Triple Triad.<\/p>\n\n<p>Released in 1999, Final Fantasy 8 was a single-player game and so too was Triple Triad, the included card game. Being a single-player game meant that you couldn\u2019t challenge friends to a quick card game. This led to continuous debates throughout my childhood about who really was the better player!<\/p>\n\n<p>To settle the debate once and for all I wanted to re-create Triple Triad in such a way that allows you to grab a friend and challenge them to a quick game!<\/p>\n\n<p>Below I will briefly run through the process of re-creating the base game, followed by a fully playable version (click <a href=\"https:\/\/philscode.com\/blog\/ff8-typescript\/#playable-game-\">here<\/a> to skip to the playable version if you like \ud83d\ude00).<\/p>\n\n<h2>\n  \n  \n  Setup \u2699\ufe0f\n<\/h2>\n\n<p>This game is going to use web technology so the Phaser framework is going to be used. It will also be piled with static assets so a Progressive Web App (PWA) is going to be created, not only to allow offline support but also greatly speed up reload times and provide installers for various mobile and desktop operating systems. To make this setup quick I'm going to utilise <a href=\"https:\/\/github.com\/yandeu\/phaser-project-template\">this<\/a> amazing Phaser boilerplate by <a href=\"https:\/\/github.com\/yandeu\">Yannick<\/a>.<\/p>\n\n<h2>\n  \n  \n  The board and cards \ud83c\udfb4\n<\/h2>\n\n<p>To start we're going to get a couple of assets for the board and mock some card assets to begin placement. Check out the gif below for the initial result.<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--tU2dIBCr--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/gziamkidj6yfdqkab81m.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--tU2dIBCr--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/gziamkidj6yfdqkab81m.gif\" alt=\"Gray identical placeholder cards being slotted on a board.\" width=\"800\" height=\"382\"><\/a><\/p>\n\n<p>Coming together already! Let\u2019s add some simple animations for placing the cards on the board and also to slide the remaining cards closer to the base of the screen.<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--_RXLl1Ca--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/74g6wn644xdhyruijht4.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--_RXLl1Ca--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/74g6wn644xdhyruijht4.gif\" alt=\"Gray identical placeholder cards being slotted on a board with animations.\" width=\"\" height=\"\"><\/a><\/p>\n\n<p>That makes it look a little less\u2026 dead. Always carefully manage your architecture correctly when considering animations. Some you may want to play asynchronously and others you want to wait until they are complete before starting another task or animation, rather than just utilising the <code>onComplete<\/code> callback to fire your next method, a more modular approach can be emitting events upon tween completion.<\/p>\n\n<p>Let\u2019s add some real cards! The cards are currently made up of 3 different assets. The card image itself is a transparent background and the blue or red back will change depending on its owner (Player 1 or Player 2).<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--HaVvLuBl--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/zu6shs65wv07kwwyel7d.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--HaVvLuBl--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/zu6shs65wv07kwwyel7d.gif\" alt=\"Cards with images and red and blue backgrounds being placed on a board.\" width=\"\" height=\"\"><\/a><\/p>\n\n<p>The above gif also demonstrates a randomisation of the cards upon reload.<\/p>\n\n<p>Finally, let\u2019s add the card ranks. Each card has 4 ranks (top, right, bottom, left) that each represents a score respective to the side they are placed on. The scores range from 1 through 10, with 10 being denoted as A. Each of these numbers is also a static asset so let\u2019s add them in!<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--LWpoblIq--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/88x21b08n0qhedyjii5a.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--LWpoblIq--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/88x21b08n0qhedyjii5a.gif\" alt=\"Cards, with alphanumeric values in their upper left corner, being placed on a board.\" width=\"\" height=\"\"><\/a><\/p>\n\n<p>All in all our card is now made up of 7 assets in total. Each of these assets needs to animate together when moving the cards in order to maintain the look and feel that a card is just a single entity. Luckily Phaser's animation <code>Tweens<\/code> accepts an array of values so the heavy lifting is done for us. Onto the gameplay!<\/p>\n\n<h2>\n  \n  \n  The gameplay \ud83c\udfb2\n<\/h2>\n\n<p>The next step is being able to detect surrounding cards. Since the board is essentially a 2-dimensional array, this task proves to be quite simple.<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--G-aAYOvO--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/il9nhcx9vq23ir09lsi8.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--G-aAYOvO--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/il9nhcx9vq23ir09lsi8.gif\" alt=\"Cards being placed near each other with a pop up displaying surrounding card names.\" width=\"\" height=\"\"><\/a><\/p>\n\n<p>The above demonstrates how once a card is placed horizontally or vertically next to another card it will detect which card or cards are surrounded by it. This is the most important mechanic of the game.<\/p>\n\n<p>From here, it\u2019s just a matter of comparing the values of the cards and changing the colour of the card or cards based on the opposing card.<\/p>\n\n<p><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--PYHenl2x--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/zz20rhew5cmcaz29dnx1.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--PYHenl2x--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800\/https:\/\/dev-to-uploads.s3.amazonaws.com\/uploads\/articles\/zz20rhew5cmcaz29dnx1.gif\" alt=\"Cards being placed next to each other and changing background colour.\" width=\"\" height=\"\"><\/a><\/p>\n\n<p>Now when we place cards around the board, if their ranks are higher in value than the card they are placed next to we will update the colour of those cards to match the opposing card.<\/p>\n\n<p>That alone gives us the basic game mechanic. Now for a little clean-up.<\/p>\n\n<h2>\n  \n  \n  Clean up \ud83e\uddf9\n<\/h2>\n\n<p>Lastly, a little bit of clean-up is in order. Firstly we are going to randomise the starting player. Then, we'll also add an indicator as to who won. Tossing in a few more animations around card flipping (tricky with 2D - but check out <a href=\"https:\/\/ourcade.co\/\">Ourcade's<\/a> tutorial <a href=\"https:\/\/www.youtube.com\/watch?v=u1wNToPU8UY\">here<\/a> - this helped a lot) and tweaking some of the asset depth and we have something playable. We'll also throw in some audio to bring it to life!<\/p>\n\n<p>Check out the preview for the final result!<\/p>\n\n<h2>\n  \n  \n  Playable Game \ud83c\udfae\n<\/h2>\n\n<p>Grab a friend and click below to play by <code>clicking or tapping<\/code> to select and place cards.<\/p>\n\n<p>Please keep in touch and reach out to me to <a href=\"https:\/\/twitter.com\/intent\/user?screen_name=philscode\">connect with me on Twitter \ud83d\ude00.<\/a> <\/p>\n\n<p><a href=\"https:\/\/philscode.com\/blog\/ff8-typescript\/#playable-game-\">Click here to play the final result. Enjoy! \ud83c\udfa2.<\/a><\/p>\n\n<h2>\n  \n  \n  PWA \ud83d\udcbe\n<\/h2>\n\n<p><a href=\"https:\/\/philscode.com\/blog\/ff8-typescript\/#pwa-\">To check out the PWA click here.<\/a><\/p>\n\n","category":["gamedev","javascript","typescript","webdev"]},{"title":"Visual Testing With Cypress","pubDate":"Wed, 20 Apr 2022 17:57:05 +0000","link":"https:\/\/dev.to\/philscode\/visual-testing-with-cypress-1o2g","guid":"https:\/\/dev.to\/philscode\/visual-testing-with-cypress-1o2g","description":"<p>For a better reading experience, you can read this article on my blog at <a href=\"https:\/\/philscode.com\/blog\/cypress-visual-testing-intro\/\" rel=\"noopener noreferrer\">philscode.com<\/a>.<\/p>\n\n<h2>\n  \n  \n  Setting the Scene\u00a0\ud83d\uddbc\ufe0f\n<\/h2>\n\n<p>It's Friday evening, the sun is shining \ud83c\udf1e, you've just deployed the latest UI changes to production. Just as you're about to put those feet up and order that takeaway\u2026<\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgnwx1nc1jw2y6wvdlps.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgnwx1nc1jw2y6wvdlps.gif\" alt=\"Phone ringing\"><\/a><\/p>\n\n<p>Those shrieking voices coming from all angles \ud83d\ude28;<\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fvl48d6ig6eb1getlir.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fvl48d6ig6eb1getlir.gif\" alt=\"Text shaking\"><\/a><\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydcsm91e54y7usmdkml3.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydcsm91e54y7usmdkml3.gif\" alt=\"Man sweating\"><\/a><\/p>\n\n<p>Clearly, something is amiss here. Let's check the build\u2026<\/p>\n\n<p>Unit testing passed \u2705<br>\nComponent testing passed \u2705<br>\nEven the E2E testing passed! \u2705<\/p>\n\n<p>It must be the backend guys, right? Us front-end devs never make any mistakes. \ud83d\ude00<\/p>\n\n<p>Time to open up those dev tools and take a deeper look.<\/p>\n\n<p>No network errors, no worrying console output. Wait\u2026<\/p>\n\n<p>What's this\u2026<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight css\"><code><span class=\"c\">\/* Todo: debug code - remove before deploy *\/<\/span>\n<span class=\"nt\">body<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">transform<\/span><span class=\"p\">:<\/span> <span class=\"n\">scale<\/span><span class=\"p\">(<\/span><span class=\"m\">0.1<\/span><span class=\"p\">);<\/span>\n  <span class=\"nl\">opacity<\/span><span class=\"p\">:<\/span> <span class=\"m\">0.1<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpudv80i1we9qy0qilfwq.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpudv80i1we9qy0qilfwq.gif\" alt=\"Person being angry\"><\/a><\/p>\n\n<p>There it is! That debug code we added that we <em>knew<\/em> we would remove since we added a <code>Todo<\/code> comment! Now the entire document body has been shrunk 1\/10th in size and its opacity has been set to 0.1 \ud83d\ude27. Meaning all the content is there, just virtually invisible to our eyes!<\/p>\n\n<p>But wait... Why did the tests pass? \ud83d\udd75\ufe0f<\/p>\n\n<p>Most of the time the tests are just grabbing an element and verifying the visibility (opacity &gt; 0) or asserting some text but never actually visually checking the page. We can't automate our eyesight after all!<\/p>\n\n<blockquote>\n<p><em>Or can we...<\/em><\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbefq3wd93h47x478bih2.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbefq3wd93h47x478bih2.gif\" alt=\"Cat putting on sun glasses\"><\/a><\/p>\n\n<p>While this was an extreme example to demonstrate the problem, it is still a very plausible scenario. This kind of issue could occur in a small but extremely important section of your application today. Like a \"Submit\" button, for example.<\/p>\n\n<p>Let's take a look at how we can use visual testing to prevent these kinds of issues from happening.<\/p>\n\n<p>As a quick side note feel free to <a href=\"https:\/\/twitter.com\/intent\/user?screen_name=philscode\" rel=\"noopener noreferrer\">connect with me on Twitter<\/a> for more posts just like this one.<\/p>\n\n<p>Now onto the testing!<\/p>\n\n\n\n\n<h2>\n  \n  \n  Visual Testing\u00a0\ud83e\uddea\n<\/h2>\n\n<h3>\n  \n  \n  Overview \ud83d\udcd3\n<\/h3>\n\n<p>We're going to use <a href=\"\/\/www.cypress.io\">Cypress<\/a> along with a free community plugin <a href=\"https:\/\/github.com\/meinaart\/cypress-plugin-snapshots\" rel=\"noopener noreferrer\">cypress-plugin-snapshots<\/a>.<\/p>\n\n<p>Cypress is going to be used to control the browser while the additional plugin, cypress-plugin-snapshots, will be used to perform the visual testing.<\/p>\n\n<p>There are a <a href=\"https:\/\/docs.cypress.io\/plugins\/directory#Visual%20Testing\" rel=\"noopener noreferrer\">vast number of plugins available<\/a> for performing visual testing with Cypress, ranging from free to paid, so be sure to check them all out before deciding. to check them all out before deciding.<\/p>\n\n<h3>\n  \n  \n  The Test \ud83d\udcdd\n<\/h3>\n\n<p>We're going to create 2 very simple tests for our demo. They will both be testing the same thing, with one being a standard Cypress test and the other being a visual test.<\/p>\n\n<p>The test will involve connecting to <a href=\"https:\/\/philscode.com\" rel=\"noopener noreferrer\">https:\/\/philscode.com<\/a> clicking the first blog post, and ensuring that the post title exists.<\/p>\n\n<p>Check below for a manual perspective of this test.<\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwgrb0fbrk9nwl6tv8ir.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwgrb0fbrk9nwl6tv8ir.gif\" alt=\"Manual test demo\"><\/a><\/p>\n\n<p>Let's get coding! \ud83d\udcbb<\/p>\n\n<h3>\n  \n  \n  Prerequisite\n<\/h3>\n\n<p>If you don't have Node.js installed head over to <a href=\"https:\/\/nodejs.org\/en\/\" rel=\"noopener noreferrer\">nodejs.org<\/a> to get set up before continuing.<\/p>\n\n<h3>\n  \n  \n  Laying the Foundation \u2699\ufe0f\n<\/h3>\n\n<p>Let's get a <code>package.json<\/code>, no questions asked by typing the below command into your chosen terminal.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>npm init -y\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>We'll install our dependencies next.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight shell\"><code>npm i cypress@7.6.0 cypress-plugin-snapshots@1.4.4 <span class=\"nt\">-S<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<blockquote>\n<p>Note the fixed versions here (this is purely to improve compatibility with this tutorial in the future).<\/p>\n<\/blockquote>\n\n<p>We'll also add a command to our <code>package.json<\/code> to make things a little easier. Drop the below into the script section.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"dl\">\"<\/span><span class=\"s2\">start<\/span><span class=\"dl\">\"<\/span><span class=\"p\">:<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">cypress open<\/span><span class=\"dl\">\"<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>Now we can fire up Cypress with an <code>npm start<\/code>.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>npm start\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>Before writing any tests we will update the auto-generated <code>cypress.json<\/code> to include a <code>baseUrl<\/code>. Replace your <code>cypress.json<\/code> content with the below code block (adjust the baseUrl as necessary).<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">\/\/ cypress.json<\/span>\n<span class=\"p\">{<\/span>\n  <span class=\"dl\">\"<\/span><span class=\"s2\">baseUrl<\/span><span class=\"dl\">\"<\/span><span class=\"p\">:<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">http:\/\/localhost:1313\/<\/span><span class=\"dl\">\"<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>We're going to head into the newly created Cypress folder in our workspace and create a file under the integration folder named <code>demo.spec.ts<\/code>.<\/p>\n\n<p>We can populate this file with our first test. This will be a regular Cypress test that verifies the blog post title.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"c1\">\/\/ demo.spec.js<\/span>\n<span class=\"nf\">describe<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">Demo Testing<\/span><span class=\"dl\">\"<\/span><span class=\"p\">,<\/span> <span class=\"p\">()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nf\">it<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">can open a blog page<\/span><span class=\"dl\">\"<\/span><span class=\"p\">,<\/span> <span class=\"p\">()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">cy<\/span><span class=\"p\">.<\/span><span class=\"nf\">visit<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">\/<\/span><span class=\"dl\">\"<\/span><span class=\"p\">);<\/span>\n\n    <span class=\"nx\">cy<\/span><span class=\"p\">.<\/span><span class=\"nf\">get<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">.post-entry:first<\/span><span class=\"dl\">\"<\/span><span class=\"p\">).<\/span><span class=\"nf\">click<\/span><span class=\"p\">();<\/span>\n\n    <span class=\"nx\">cy<\/span><span class=\"p\">.<\/span><span class=\"nf\">get<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">.post-title<\/span><span class=\"dl\">\"<\/span><span class=\"p\">)<\/span>\n      <span class=\"p\">.<\/span><span class=\"nf\">should<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">be.visible<\/span><span class=\"dl\">\"<\/span><span class=\"p\">)<\/span>\n      <span class=\"p\">.<\/span><span class=\"nf\">contains<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">Web scraping to create an api in 3 minutes!<\/span><span class=\"dl\">\"<\/span><span class=\"p\">);<\/span>\n  <span class=\"p\">});<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>Now we will select the <code>demo.spec.ts<\/code> file from the Cypress file selector that popped up after we ran our <code>npm start<\/code> command. Check below to see the test running.<\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foah5u1bzkzz7wdsdbhfo.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foah5u1bzkzz7wdsdbhfo.gif\" alt=\"Cypress test passing\"><\/a><\/p>\n\n<p>Great! We have a passing test! Let's take a look at what happens if we set the\u00a0.post-title to be:<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight css\"><code><span class=\"nc\">.post-title<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">transform<\/span><span class=\"p\">:<\/span> <span class=\"n\">scale<\/span><span class=\"p\">(<\/span><span class=\"m\">0.1<\/span><span class=\"p\">);<\/span>\n  <span class=\"nl\">opacity<\/span><span class=\"p\">:<\/span> <span class=\"m\">0.1<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>Now, check below to see the same test running again.<\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzxqxpo6bpk5qqyt609e.gif\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzxqxpo6bpk5qqyt609e.gif\" alt=\"Cypress test passing\"><\/a><\/p>\n\n<p>Even though the title seems invisible, the test still passes. This is not ideal.<\/p>\n\n<h3>\n  \n  \n  The Visual Test \ud83d\udc41\ufe0f\n<\/h3>\n\n<p>Let's configure the plugin.<\/p>\n\n<p>Jump over to the <code>cypress\/integration\/plugins\/index.js<\/code> and replace the contents with the following.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"kd\">const<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">initPlugin<\/span> <span class=\"p\">}<\/span> <span class=\"o\">=<\/span> <span class=\"nf\">require<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">cypress-plugin-snapshots\/plugin<\/span><span class=\"dl\">\"<\/span><span class=\"p\">);<\/span>\n\n<span class=\"nx\">module<\/span><span class=\"p\">.<\/span><span class=\"nx\">exports<\/span> <span class=\"o\">=<\/span> <span class=\"p\">(<\/span><span class=\"nx\">on<\/span><span class=\"p\">,<\/span> <span class=\"nx\">config<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nf\">initPlugin<\/span><span class=\"p\">(<\/span><span class=\"nx\">on<\/span><span class=\"p\">,<\/span> <span class=\"nx\">config<\/span><span class=\"p\">);<\/span>\n  <span class=\"k\">return<\/span> <span class=\"nx\">config<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">};<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>Add the following import to <code>cypress\/integration\/support\/index.js<\/code>.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"k\">import<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">cypress-plugin-snapshots\/commands<\/span><span class=\"dl\">\"<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>For now, we will revert the <code>CSS<\/code> changes we made.<\/p>\n\n<p>Let's add a second test to our <code>demo.spec.js<\/code>.<br>\n<\/p>\n\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight javascript\"><code><span class=\"nf\">it<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">visually check a post<\/span><span class=\"dl\">\"<\/span><span class=\"p\">,<\/span> <span class=\"p\">()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">cy<\/span><span class=\"p\">.<\/span><span class=\"nf\">visit<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">\/<\/span><span class=\"dl\">\"<\/span><span class=\"p\">);<\/span>\n\n  <span class=\"nx\">cy<\/span><span class=\"p\">.<\/span><span class=\"nf\">get<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">.post-entry:first<\/span><span class=\"dl\">\"<\/span><span class=\"p\">).<\/span><span class=\"nf\">click<\/span><span class=\"p\">();<\/span>\n\n  <span class=\"nx\">cy<\/span><span class=\"p\">.<\/span><span class=\"nf\">get<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">.post-title<\/span><span class=\"dl\">\"<\/span><span class=\"p\">).<\/span><span class=\"nf\">toMatchImageSnapshot<\/span><span class=\"p\">({<\/span> <span class=\"na\">imageConfig<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span> <span class=\"na\">threshold<\/span><span class=\"p\">:<\/span> <span class=\"mi\">0<\/span> <span class=\"p\">}<\/span> <span class=\"p\">});<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre>\n\n<\/div>\n\n\n\n<p>After running this test it will pass and an image will be saved in the workspace. Check it out below.<\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw5eltkg2503yrci11ym.png\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw5eltkg2503yrci11ym.png\" alt=\"Screenshot of text\"><\/a><\/p>\n\n<p>This image will now be our baseline image to be used to compare future test runs against. Let's reintroduce our bad <code>CSS<\/code> and re-run the tests.<\/p>\n\n<p>We now have a failing test and we can also get a look at the expected and actual image results. There is also an image diff available so you can see an overlay of both images with highlighted areas where potential differences lay.<\/p>\n\n<p><a href=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favcdkdu334ymsoxj3wmb.png\" class=\"article-body-image-wrapper\"><img src=\"https:\/\/media.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favcdkdu334ymsoxj3wmb.png\" alt=\"Passing visual test\"><\/a><\/p>\n\n<p>Success! \ud83d\udc4d<\/p>\n\n<h3>\n  \n  \n  Conclusion\n<\/h3>\n\n<p>Visual testing can be used to test an entire page, individual elements, on a component-by-component basis, and so on. Like any tool developers utilise, the usage will depend on your own use cases. Large screenshots can easily become brittle over time so consider your tests (and thresholds) carefully.<\/p>\n\n<p>Remember to check out all the <a href=\"https:\/\/docs.cypress.io\/plugins\/directory#Visual%20Testing\" rel=\"noopener noreferrer\">available Cypress visual testing plugins<\/a> to learn more!<\/p>\n\n<p>Keep in touch by <a href=\"https:\/\/twitter.com\/intent\/user?screen_name=philscode\" rel=\"noopener noreferrer\">connecting with me on Twitter<\/a>.<\/p>\n\n<p>Check out my blog too, <a href=\"https:\/\/philscode.com\" rel=\"noopener noreferrer\">philscode.com<\/a><\/p>\n\n<p>Source code: <a href=\"https:\/\/github.com\/philipgriffin\/cypress-visual-testing\" rel=\"noopener noreferrer\">Github<\/a><\/p>\n\n","category":["webdev","testing","javascript","tutorial"]}]}}