<?xml version="1.0" encoding="UTF-8"?><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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Vikas Maini on Medium]]></title>
        <description><![CDATA[Stories by Vikas Maini on Medium]]></description>
        <link>https://medium.com/@vksmaini?source=rss-360cab5a3918------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*6OhytimZiSFIRpf2WHOfVg@2x.jpeg</url>
            <title>Stories by Vikas Maini on Medium</title>
            <link>https://medium.com/@vksmaini?source=rss-360cab5a3918------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 08:16:23 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@vksmaini/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Pixel Perfect UI at Scale]]></title>
            <link>https://medium.com/gojekengineering/pixel-perfect-ui-at-scale-664d190f7630?source=rss-360cab5a3918------2</link>
            <guid isPermaLink="false">https://medium.com/p/664d190f7630</guid>
            <category><![CDATA[ux]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[ui-testing]]></category>
            <category><![CDATA[ux-design]]></category>
            <category><![CDATA[ux-engineering]]></category>
            <dc:creator><![CDATA[Vikas Maini]]></dc:creator>
            <pubDate>Tue, 04 Jun 2019 04:04:07 GMT</pubDate>
            <atom:updated>2019-06-04T06:27:48.447Z</atom:updated>
            <content:encoded><![CDATA[<h4>How we maintain UI tests for our design language system on iOS.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-R91HlVgXmzhTRuXF4v8lA.gif" /><figcaption><a href="http://bit.ly/2Zbqr97">gojek.jobs</a></figcaption></figure><p><em>By Vikas and Abhinav from UX-Engineering.</em></p><p>Developers and designers are entities who always try to do their best to build a beautiful product for users. But this is not always enough to ensure that the carefully-crafted designs look exactly the same on a user’s device.</p><p>To make sure developers build UI close to the designs, we have to understand the designs first, discuss with designers, and double check on Zeplin after development. We also have to make sure the UI doesn’t break in different scenarios, code changes etc.</p><p>This all takes a tremendous amount of time and is not always feasible.</p><blockquote>Wouldn’t it be great if we could automatically check if everything is as the designer intended?</blockquote><p>In this post, we’re going to discuss one way of solving this gap between designed UI and developed UI efficiently. (In other words, the gap between developers and designers.)</p><h3>Asphalt — Maintain Consistency</h3><p>We’ve been talking about our design language system, <a href="https://asphalt.gojek.io/">Asphalt</a>, for some time now. It was built to ensure that our UI is consistent and easy to use across GOJEK’s vast array of products and services.</p><p><strong>Recap: </strong>Asphalt is GO-JEK’s very own design language system. It aims to unite developers, designers, and all other functions across the org for consistency and efficiency. It is the foundation to be built upon.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vjOObYug7_ke5Sxt4XiHkQ.png" /><figcaption>Asphalt Components</figcaption></figure><h3>The problem?</h3><p>In Asphalt, we treat components like reusable Lego blocks. This helps in building components at a fast pace.</p><blockquote>Build UI like Lego</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gb81-moXFPxUvALR8AgkMA.gif" /><figcaption>Shuffle Card lego blocks</figcaption></figure><p>In the process, developers tend to update or extend the functionality of a component, and they need to be able to do this without causing side effects to other components.</p><p>We felt the need for a monitoring system to check UI components with each commit/change a developer makes, that informs the developer of the impact of the change. We also needed to run this system on autopilot mode, check our components frequently and detect any anomalies. The objective was not to replace human operators, but to assist them. This is what we seek to achieve with our UI Components Library.</p><blockquote>To achieve this goal, we settled on snapshot tests.</blockquote><h3>Snapshot Testing</h3><p>Conceptually, snapshot testing is a simple idea: take a screenshot of your user interface and store it as a reference image. Then, make your tests take screenshots of the same UI and compare it to the reference image. Once the test is complete, let the developer running the test see the difference and the affected changes, and decide if the changes are correct.</p><h3>Implementing Snapshot tests on iOS</h3><p>We use XCUITest framework that ships with Xcode, which allows you to easily <a href="https://developer.apple.com/videos/wwdc/2015/?id=406">develop UI tests</a> that reflect users’ interaction with the application.</p><h4>Finding the diff between two screenshots</h4><p>We use <a href="https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html">Core Image filters</a> (CISubtractBlendMode and CIAreaAverage) to find the diff between a reference screenshot and a screenshot of the current state of a component.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/64089e9ab59304cc066408bbb519985b/href">https://medium.com/media/64089e9ab59304cc066408bbb519985b/href</a></iframe><p>In the code sample above, we use a UIImage extension where DifferenceFilter subtracts the background image sample colour from the source image sample colour and gives us the difference image. Both the input and output image we get is a CIImage.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cb04e2f716ed3ffc12efd08eac2599c1/href">https://medium.com/media/cb04e2f716ed3ffc12efd08eac2599c1/href</a></iframe><p>We inject the difference image to AverageFilter, which returns a single-pixel image that contains the average colour for the region of interest.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/15838700bc90ad1c853bbfd4ce5ae4a5/href">https://medium.com/media/15838700bc90ad1c853bbfd4ce5ae4a5/href</a></iframe><p>We run the averageFilterOuputImage through the firstPixelColor function shown above. This returns UIColor which we use to conclude whether it is all black or not. If it is not completely black, then we’ve got a difference in our source image.</p><p>In our UI tests, we launch the screen with the component we need to test in our demo app and call this image diff algorithm. If there is a difference, we fail the test and generate a report showing the diff.</p><h4>Observing the results</h4><p>Here’s an example that we recently experienced.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/314/1*BNZxF1y9iHBilmWPfN5_ng.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/149/1*0dGJOt-08GHuzysbuPikhg@2x.png" /><figcaption>1) Reference screenshot 2) Failed test screenshot highlighting the difference</figcaption></figure><p>We have this button class, TextButton, which is given a PrimaryState and is responsible for updating its background colours, text colours, enabled state, and text. We include this button on most of our views in the app for primary interactions. We wanted to change some of the rendering logic in the button, but we inadvertently broke another part of our app that we didn’t realise was using TextButton too.</p><p>As we can see from the screenshot, the test highlighted that the border colour was different between the two images which was not what we intended. Snapshot tests saved us from committing a mistake that would have led to bugs across the app in different products and caused delays in our shipping cycle.</p><h4>Managing reference screenshots</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1XPCZDykTOeRhhUfmte0Zg.png" /><figcaption>Reference images for TextButton</figcaption></figure><p>We keep our screenshots segregated by device type. We have a naming convention which goes <em>device_resolution+os_version</em>. Above, you’ll see the 640+1136+10 folder and reference images for TextButtons. These images are generated by running the tests on iPhone 5s( which has a device resolution of 640x1136 and runs iOS 10). Similarly, we generate images for multiple available devices.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4a4995d90514fb139cea74f41f292e5d/href">https://medium.com/media/4a4995d90514fb139cea74f41f292e5d/href</a></iframe><p>Sample code for ButtonTests above has a <em>folderName</em> variable and <em>testPrimaryButton()</em> function. When we run the test above for the first time after building the component, we generate these images from identifier <em>TextButton-primary_cover_normal_state</em> appending the screen dimensions and save them under the folder named <em>ButtonTests. </em>This way, we’re generating reference images from our UITest code only. After adding these folders in the bundle we run the tests again and this time screenshotHelper has a reference image in the bundle to test them against the UI screenshot. This way we ensure a consistent pattern <em>(like the naming convention, bundle structure etc)</em> for all reference images and make the process faster.</p><h3>So does it work?</h3><p>The advantages are that you can easily test complex structures without writing much code, you get good warnings when something changed, and you can easily update this test.</p><p>The disadvantages are that from just reading the test, it is not always clear what is tested and what the expected behaviour is. Sometimes, the created snapshot is so complex that you overlook wrong assumptions that then end up as expected results. As it is so easy to update snapshots, wrong stuff can sneak in.</p><p>One challenge is, at around test count 170, Xcode or xcodebuildwelcomed us with the following error:</p><pre>Waiting for accessibility to load<br>Wait for app to idle<br>    App event loop idle notification not received, will attempt to continue.<br>    App animations complete notification not received, will attempt to continue.</pre><p>After this error, all the subsequent tests fail with another message:</p><pre>Waiting for accessibility to load Assertion Failure: testUITests.swift:21: UI Testing Failure — App accessibility isn’t loaded</pre><p>We’ve looked around Google and <a href="https://www.pgs-soft.com/blog/ios-ui-testing-and-why-it-does-not-always-work-a-k-a-pushing-the-limits-of-the-xctest-framework/">found here</a> the details about this issue. We’ve tried multiple workarounds mentioned in the blog, also tried to make multiple targets and schemes for a number of tests, but in vain.</p><p>Until Apple finally fixes this issue, we’re keeping the count of tests lower than 170 😑 (If you know why this happens, feel free to leave a comment below).</p><p>One more challenge is to run the tests faster. Currently, we’ve disabled all the animations by setting the following in the Appdelegate <em>didFinishLaunchingWithOptions</em> function. Disabling all animations makes the tests more reliable and makes them run quicker.</p><pre>if ProcessInfo.processInfo.arguments.contains(&quot;AsphaltUITests&quot;) {</pre><pre>UIView.setAnimationsEnabled(false)</pre><pre>}</pre><p>Flaky Tests<strong> </strong>also need a special mention. As our test suite is expanding we’re starting to face the problem of one or two tests failing randomly. To solve this kind of misbehaviour, we’re currently experimenting with <a href="https://github.com/lyndsey-ferguson/fastlane-plugin-test_center">fastlane-plugin-test_center</a> multi-scan functionality to rerun only those fragile tests, saving us time taken to rerun the complete suite.</p><blockquote>Note: this feature does not support Xcode parallel tests functionality yet but it is WIP <a href="https://github.com/lyndsey-ferguson/fastlane-plugin-test_center/issues/10">here</a>.</blockquote><h3>What’s next in Snapshot Testing?</h3><p>We’re planning to auto-generate UITest classes by analyzing the UIComponents classes. Most of the times the code for UITest class is dumb. Reach up to the component, take a screenshot of the view assigned accessibility identifier and match it with the reference image(whose filename is generated by class name). Our first step is to autogenerate these dumb steps and then move up to write stencils for different behaviours of components like swipe, buttons, max data, min data and many more. Of course, we’ll not be able to cover all testing scenarios with this process, but this will remove a lot of generic testing scenarios we write for a UIComponent.</p><h3>In Summary</h3><p>Overall, Snapshot tests have served us well, but a tool is only as good as its user. So it’s important to make them granular, readable and treat them just like you would treat normal tests. Combining these with a culture of thorough code reviews has paid great dividends for us.</p><p>I hope this gives you a clear picture of what snapshot tests are and how they can be used as a part of a comprehensive testing strategy.</p><p>As designers and developers, there are always things to be improved and processes to be streamlined. At GOJEK, this is something we realise and attempt to overcome with a culture of close collaboration between teams. If this culture is something you can contribute to, check out <a href="http://bit.ly/2Zbqr97">gojek.jobs</a> — we’re looking for smart minds to help us build Southeast Asia’s SuperApp 🖖</p><figure><a href="http://bit.ly/2Zbqr97"><img alt="" src="https://cdn-images-1.medium.com/max/892/1*PdKKvtpvxTQcbMlDvIlFzQ.png" /></a><figcaption>gojek.jobs</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=664d190f7630" width="1" height="1" alt=""><hr><p><a href="https://medium.com/gojekengineering/pixel-perfect-ui-at-scale-664d190f7630">Pixel Perfect UI at Scale</a> was originally published in <a href="https://medium.com/gojekengineering">Gojek Product + Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[1 app, 18 products — A journey from monolith to a microapps codebase]]></title>
            <link>https://medium.com/gojekengineering/1-app-18-products-a-journey-from-monolith-to-a-microapps-codebase-8ea30d070148?source=rss-360cab5a3918------2</link>
            <guid isPermaLink="false">https://medium.com/p/8ea30d070148</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[architecture]]></category>
            <dc:creator><![CDATA[Vikas Maini]]></dc:creator>
            <pubDate>Mon, 19 Mar 2018 05:39:12 GMT</pubDate>
            <atom:updated>2018-03-20T10:43:55.211Z</atom:updated>
            <content:encoded><![CDATA[<h4>Here’s how we radically changed our iOS codebase while processing 100+ million orders every month</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cI9JEEcm3bBZreqMhQBlow.jpeg" /></figure><p>GO-JEK. “<em>An Ojek for Every Need</em>“.</p><p>Like literally, EVERY need.</p><p>From food, transport, massage, cleaning, payment, shopping, logistics, delivery, medicine, entertainment, you name it, we have it.</p><p>Oh, and in case you’re wondering, an<em> “ojek” </em>is a motorcycle taxi, or cart bike. It’s a licensed form of transport in some countries.</p><p>We have around 18 services up and running across our platforms and more are coming. The amount of services GO-JEK helps you with is nothing short of phenomenal.</p><blockquote>This is the story of how we built our iOS app:</blockquote><h4>The Beginning</h4><p>Our iOS codebase was Monolithic. By nature of our vastness, a lot of code was jumbled across products. The code was so convoluted, no one knew how a small change in their code would affect the others.</p><p>Inevitably, there was a ripple effect; solving a bug would raise a new one. Debugging was not easy. It took almost 45 minutes to build, whether we were building from scratch or just changing a single line of code.</p><p>The bizarre amount of time it took to test the smallest changes was proving to be cumbersome. This time wasted was worse on our Continuous Integration pipeline. And it was fairly obvious because we were handling nearly 18 products inside a single repository, and separate teams were simultaneously working on the same codebase. So much so, we used to have a team whose only responsibility was to fix bugs for the entire app.</p><p>Every new iOS developer at GO-JEK spent a few weeks with this team, fixing bugs and learning the codebase, before being assigned to a different product team. If we wanted to improve the codebase, we first need to figure out how to spend less time on bugs.</p><h4>The VIPER Phase</h4><p>We needed to reinvent ourself. After analysing our main issues, we decided to remove all cross-product dependencies and restructure our products using VIPER.</p><p>We decided to build GO-PAY and GO-POINTS, with this new structure. These products would lay a foundation for future products and become a template for refactoring existing ones. It was important for us to choose the architecture best suited to handle these many products together. Initially we chose VIPER, and here’s why:</p><p><strong>Pros:</strong></p><ul><li>Code becomes much more testable</li><li>Separation of components really well</li><li>Very high encapsulation</li><li>Easy removal of any feature</li></ul><p><strong>Cons:</strong></p><ul><li>Two-way communication between modules is not possible</li><li>Difficult to step through while debugging since everything is abstracted behind a protocol</li></ul><p>Irrespective of the cons, we accepted this architecture and continued the transition for other products as well. But to start this for existing products, we needed a firebreak.</p><h4>Firebreak — Modularisation</h4><p><em>An obstacle to the spread of fire</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NmZjfAchQimT3Sh05dEa1A.png" /></figure><p>We pinned down the architecture and got the implementation right in products. But, we needed to modularise the whole approach. We were still counting 45 mins on average of compile time. This firebreak exercise was going to literally stop feature development for a few weeks and let us just focus on modularisation tasks. It was a hard decision at the time, but to get this right, we had to get things right internally. Features, and product development comes only after practicing what we preach — adhering to the principles of good software development practices.</p><blockquote>“It’s not about being the first to market, it’s about being the first to get it right.” — Benny Xian</blockquote><p>This one step did wonders for us in iOS. On Mobile Consumer App, our main goal for Firebreak, was to break down the single repo into multiple product repos as a part of modularisation task.</p><p>Every iOS developer or a small team became POCs for each product. We identified each product’s files, assets, fonts, resources and moved them out to separate repositories. Sometimes we had to make duplicate files in separate repos as both products were using them. But we were ready to hold this duplicity for now. Home, Settings, Help, History, Tabs, Navigation stack, Analytics were still at the core of GO-JEK holding 18 products through CocoaPods.</p><h4>Advantages of breaking down the repos gave us the following benefits:</h4><ul><li>Pushes for platformization of the Consumer App. All essential services to be extracted out in platform library. This includes Network, Notification, Remote Config, Analytics, UI Components,</li><li>Gives more independence to each product on moving quickly without being affected by other product changes</li><li>Easier to track quality and impact by a product on the integrated app. E.g. app size, code quality can be tracked at product repo level.</li><li>Easier rollback of product specific changes</li><li>Enforces coming up with better API contracts/generalised solutions, because of module separation.</li></ul><h4>Some of the retrospective points we collected from our team:</h4><ul><li>Satisfied with Firebreak</li><li>Compile time down on iOS</li><li>Many tech clean up tasks pending, need more time on Firebreak/Tech clean up activities</li><li>[Action] have a small team continue on pending critical tasks</li><li>Build process with pods is getting complex</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/294/1*y06FYCX1kgiTgWxaMsvHRw.png" /><figcaption>Host apps with their products</figcaption></figure><p>Clearly, developers were happy with the progress and they don’t have to wait for the main codebase to compile and test the product. Now, developers host apps to run and test their products in complete isolation.</p><p>This gave tremendous freedom fo the teams to kick start feature development in full throttle.</p><p>But we were not done yet. The main GO-JEK app was still taking about 40 mins on average to compile all product’s pods and the main code. We can’t get the leverage to have another firebreak to fix more things, so we continued with it. But we knew the problem was at core.</p><h4>Launchpad</h4><p>Our main goal for the iOS app was to boost developer productivity. All the measures we took in this endeavour helped us reduce the time taken to build, test and ship out new features and ensured quick bug fixes.</p><p>The main motivation behind separating product codebases was to make product teams independent of each other. This also resulted in immensely reducing the time taken to build individual products. Since each team was no longer dependant on other products, we were able to ship independently.</p><p>During this time, we introduced the concept of dummy launcher apps. These were single screen apps which sort of had an index of a product’s various screens. Every product repository had its own dummy app and product developers used these apps to test the features they were building.</p><p><strong>There were few drawbacks to this approach:</strong></p><ul><li>You cannot test a product in complete isolation from other products in the real GO-JEK environment</li><li>Testing and debugging analytics events were not possible</li><li>Testing and debugging remote notifications and deeplinks were not possible</li><li>Testing a complete booking flow — from placing an order to viewing the history were not possible</li><li>Testing logout/login events were not possible</li></ul><p>This meant in order to fully test a feature, developers had to build the actual GO-JEK app (with all products) with their product code in it. And debugging and fixing bugs required multiple app builds. Preparing production releases required a synchronised effort from all teams since it was complicated to automatically merge many codebases together in a reliable way.</p><p>To fix these, we came up with <strong><em>Launchpad</em></strong>. It was a 6-week project where we re-wrote the GO-JEK app (barring actual product code).</p><p><strong>The main vision and goals behind Launchpad were as follows:</strong></p><ul><li>Sandbox all product codebases into their own repositories</li><li>Make GO-JEK fully independent of products</li><li>Wrap the GO-JEK app into a framework that can be imported into products</li><li>Allow products to be tested in isolation in the real GO-JEK environment</li><li>Come up with unified APIs to integrate a product into GO-JEK</li><li>Allow for easy removal of products from GO-JEK</li><li>Allow a product or a group of products to be submitted as a stand-alone app to the App Store without major code changes</li><li>Allow products to be individually profiled and audited for quality and performance</li><li>Export APIs, allowing products to show dynamic components in the home screen without modifying the GO-JEK codebase</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/490/1*bBivuMfpC_9qHUbnJzjs-Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/416/1*UJ6bK6qs5TGw1mI9LwHolw.png" /></figure><p><strong>A clean build succeeded in just 8 minutes. We’re actively improving this further with compiler optimisation tweaks.</strong></p><p>Throughout the past year or so we’ve seen the codebase transforming radically and maturing gradually. This helped us shift our focus to build some cool features which improves the overall experience of the app. To sum up the post, as John Johnson famously said,</p><blockquote>“First, solve the problem. Then, write the code”</blockquote><p>And yes <strong>We’re hiring!</strong> For any roles in Engineering, Design or Product Management, or anything at all, visit <a href="http://gojek.jobs/">gojek.jobs</a>. Come solve amazing engineering challenges.</p><p>Happy hacking :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ea30d070148" width="1" height="1" alt=""><hr><p><a href="https://medium.com/gojekengineering/1-app-18-products-a-journey-from-monolith-to-a-microapps-codebase-8ea30d070148">1 app, 18 products — A journey from monolith to a microapps codebase</a> was originally published in <a href="https://medium.com/gojekengineering">Gojek Product + Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>