Build an automation framework... with a developer mindset | Aditi Mulay | #SeConfLondon

Build an automation framework... with a developer mindset | Aditi Mulay | #SeConfLondon

Show Video

Hello. Everyone welcome. To the selenium conference, divan and my track I will. Be talking about leveraging. Development, principles and how, to use them to build our automation, frameworks, a quick. Intro before we get started my name is Aditi, Malay I have. Been working, in the field of automation for, quite some time before, that I used to be in the development field and, just to give you an idea about how long I have been in this field my, first project that I've worked in automation was, with selenium, 1.11. It, there was no webdriver at that time I was using salomon 1.11, at the time so. That's how long I have been working, with automation I love this field and I. Have, been working in education. Advertising. And currently I work for a government contracting, firm called, cars and solutions it's based in Northern Virginia, so, I'm from the US right. Now, quick, way to reach out to me is LinkedIn, Twitter and, we, may not have time to have question-answer session today because. You guys will be in a rush to get your lunch so, if. You would like to please welcome and. Meet me at the office hours in the office next door. With that quick intro out of the way let's, quickly get started. This. Is my agenda for today just a few things first. Is I just want to talk about automation, framework what I mean when I talk about automation framework, and, then. Comes the development principles so. The development principles as you might be aware of I'm going to talk about the, object-oriented, programming principles and, the. Big part I won't choose sort of which I hope it comes across is it's not just an intro, to oops, I have, some actual examples which, I have used in my automation, frameworks, and those. Are the things I would hope that you take away when you leave this talk and. That's a quick look at the agenda and, let's, get started. So. Automation, framework a lot, of times when I talk to people and our SEM what type of automation framework are you using. Mostly, the answer is just selenium. So. That's, a great one it's but it's a great tool it doesn't, completely, give you access to the complete, testing framework that you might need you might be doing back, in testing API testing so there's a lot more involved in the framework but yes the first building, block of my automation framework is always. Selenium. Also. One thing I do is rather. Than using selenium directly out of the box, most of the teams I have worked with they, use a wrapper on top of it just makes. It easier for us to combine three lines of selenium code into one line and that's. How we use our wrappers, I. Have, mostly used selenium, if I want to do it out of the box I used it like a proof of concept just. To say that oh I want to test this application, I will write a few lines of selenium code just to show my managers, that this is how we can use this particular tool, so. That's the first building block another, thing and the most important one is our test, data all, of our tests require, test data but it comes in different formats you, can get your test data from XML Excel. JSON. CSV. Properties, I don't, know there may be another ways of even getting test data so. Getting, that test data retrieving, it from those files and then using, them in your scenarios. Or test cases that is one thing that also needs, to be part of your automation framework. Then. Comes. Utilities. So these are what I call my helpers you always. Need to have tons of these helpers, to get your automation framework up and running file. Readers so file readers I mean not just the data file readers but. If your application, is uploading, downloading PDF. Files you need a way to read those files. Databases. If the way your application is, supposed to be tested is check, something on the UI and then, make sure that's the same data that is stored in the database in that, case you will need some database utilities, to make those connections data. Pooling and, also to run those queries validation, queries and. APs, you, can have your own API test harness or you could use those api's and, web services, as part of your data, set up data teardown methods so. You will need some sort of client for that and, lastly and, there's tons of other things by date format string comparisons, lot, of things that we do as part of our automation testing, those. Can also become these a lot part of these utilities. Now. This, is one important, thing traceability, when. We write test cases it's not just about writing a test case and being done you, have to always map it to a particular story, or feature in your code, when, you are coming, up with those requirements and testing things so. Trick tagging is an important, part of that you. Can use tagging, to be sort of like a live documentation. It. Can say oh this particular one is related to JIRA tickets so and so that's one way or this.

Particular Scenario is related to this module or this, scenario is a positive, test versus a negative test or a, smoke, test versus a sanity test you can add any number of tags to your test cases and that, again becomes your documentation. This. Feature is provided by koo kumba also, if you are using that but if not then you can actually build, your own tagging, framework, then. Other. Thing of tagging, are the importance, of tagging, is execution. You, can use this as a subset, of, tests. That you want to run you might have hundred tests out there but. You need to run only 24, your sanity check so, you can tag those tests. As sanity, and just run, those tests so. This is one important, part of your automation framework and once. You are ready with the execution, part you come comes the reporting reporting. As you know we get screenshots. From lot of tools out there there's J unit there's an, N number of other tools cucumber which provides its own reports, but. In, sometimes, you need more than what those reports are providing in my, case we wanted a capability, to do repeat yo recording of every, test that runs so I have like small video files generated at, the end of my test this. Is helpful when I have like 100 tests out there and I don't have the time to babysit each and every one and look at them how they are running on a regular basis so in, those cases when there are failures I just go back to view the videos and I can figure out where exactly in, the test case or on, the UI and my test field so. That's again a cool capability, to have as part of your automation, framework and then. Last but not the least browser, man meant selenium. Selenium, grid provides us the speciality already, but, you need to make sure that your application. Your automation. Framework is configured, to use that capability out there, so. If you look at all these things you. Can see that that's a lot of coding that we have to do and, it's not something that we can do on day one and say by end of the day I will be done my automation framework is ready and, I can start testing writing test for that this. One requires a little bit of design. Design. Review, design, the architecture. And lots. Of coding, so. That is where the idea for this talk came in that we are doing so much coding we are the, coding is almost comparable to, the one that a software developer does for, his application, code and they, use their development principles so. Why can we not use the same thing on our automation, frameworks while we are designing them so. Here, is a look at. Okay. Here's. A look at the object-oriented. Principles that, most of the developers are using, they. Are using this for languages. Like Java C. Sharp and even. Though I'm, talking about these oops, in, terms of Java that's where I have worked mostly you, could take some of these examples and bring. Them back to your scripting tools like Python, or Ruby also, so. Even, though there are object-oriented, not object-oriented, programming you. Can still leverage the same examples, in your scripting, frameworks, so. These are the four main pillars of oops, we. Are all fond of acronyms, so oops is the way. To go for this. Abstraction. Encapsulation. Inheritance and. Polymorphism, these. Are the four pillars or the fantastic, four as I call them in our, building, frameworks if, time. Permits I will like to take just like two minutes to talk about the DRI principle, but. The most important, ones I want to convey as part of this as the ops ones so. Let's get started with abstraction. This. Is the scientific, definition for, abstraction. Process. Of hiding all data but, the relevant one and that, helps with reducing the complexity, and increasing, efficiency, but. What, does it mean in sort of like layman's terms for me to understand. And this is what I understood from that, start. Off simple but. Provide space later, on to, add more complexities, okay, so. Let me give you an example of this some. Real-life, example which happens, in my household it might happen in yours too but. This happens in my house quite a few times I. Have a son who, is into, reading, and he, loves playing video games so. Half, the time I would say not even half the time 99%. Of the time his mind is thinking about the next Percy. Jackson adventure or, what sig is going to do in Minecraft and so, on so he's a pretty busy guy that way so. One. Day I had this chat with him I said, hey it's, been a long time since I haven't done any baking, and I love baking cakes so. Maybe tonight since I'm done with my presentation preparation. And everything how, about I get some let's. Bake a cake and next, week actually two cakes not just one one, with a vanilla flavor for you and a chocolate flavor for my younger, daughter, and.

Can, You help me with that and could you just grab the cake floor from the pantry and the eggs from the fridge, so. Being, the good kid that he is he immediately jumped, up from the sofa ran it towards the pantry opened, the door and sat, staring inside, for quite a long time and then. He comes to me and says so. Mama what. Exactly, did you want me to get from the pantry so. That. Is when I realized that that, whole one I thought it was a conversation but it turned out to be a monologue, in. That whole monologue he didn't get what, I was trying to get him to do he, lost track of the, main goal I wanted to achieve and he just was like okay I have no idea what she wanted me to do here in. That case it would have been much easier much, better if I had told him to just say okay can you just get so-and-so items, from the fridge and that would have been much more efficient, from. His point of view so. Anyways basically this is what happens even in our software field we, are trying to put too, many details into our design, too, many details are going. Into our requirements, and so on and that causes lot of problems because then we certainly lose sight, of the goal that we want to achieve so. That's where abstraction, comes in. And the, best example of abstraction, is in our selenium-webdriver. How. Many of you here have taken a look at our selenium webdriver code, can, you show me, okay. I can literally count them. On the fingers of my hand so, as. We know we don't need to look at our code because the, the. Selenium webdriver is so cool, they have enough documentation that, we don't need to deal with the code but. The, best example of forrester abstraction, is right there in. Our, copes. Okay. Okay. There it is this. Is an interface which is our webdriver this, is a quick peek I have actually sort of moved. A little bit code around so that it's easier for me to place it on this slide. You. Can see three methods out here get, close, and quit and all, of us who use selenium are almost very familiar, with these methods yet we'll open up a new page with the URL specified, close. Will close that browser window and quit, will stop, the bra driver. Itself so. These are the three methods which are really important to all of us which we all use but, none. Of us needs, to know the complexity, of it how they are implemented how, actually does this work we, don't need to know as long as we know that it works so. That's why this is this particular entity, is called an interface, where. You are declaring, the methods but not giving all the details out here okay. So you, are hiding the details from the user now. Where exactly is, that detail in so. If you look at the selenium, code you will see that this is the structure of it very, basic a high-level structure, you. Have something called as a remote driver this. Is the class where all the complexities, are there so, the high-level big picture is in the webdriver but. All the complexity is the implementations. Of each and every method that we use is, in the remote driver and, most. Of us would not even have been aware that there is something called as a remote driver and this Li we looked at the code as such we. Just deal with the webdriver which is what we are interested in so. This is how abstraction, works you take away the complexities, and you, give them a simplified, structure to, then build on now. When chrome and frogs driver Firefox, driver come, into picture they, don't need to define the same code again it's already, there in the remote Trevor they just extend, the remote driver class and they are all set so. This is one example of. Interfaces. In webdriver, which, uses, abstraction, concept but. This was sort of a ready bite example for me I don't need to do anything for this it was already written by the Simon. And Friends. So. What. Did I do when I had to do I deal with abstraction, so. Here is an example I was. Given a requirement, where I had to write a report j-unit. Already provides report as I've mentioned koo, kumba it provides those reports but, my team needed something more customized, they wanted more details added to the report which was generated after the tests were executed, so. I was told to write a report in HTML format and. Some. Additional data was supposed to be added to that report so. I was like ok let me get started immediately I could have started off with just one, class which, has my complete implementation, getting. The data and putting it into an HTML file, excuse. Me so, that, was the easy way but, then I was thinking that what. Happens today they told me it's an HTML report tomorrow.

They Can come by and say I need, an Excel report or I need it in a PDF format, so. I wanted to decide. It in such a way or design it in such a way that in, future when. The requirements changed, or there was an enhancement, required, my same code would still work so, this is what I did I created. A class called report it, has two methods generate. Report and convert, to format I already. Implemented, the generate report in that report class that. Method, was basically it was taking, all the data I wanted and putting it into a map a key, value pair format, so. That's all that method did the. Convert to format method was the one which I kept abstract, and, let me show you the piece of actual, code out here it's again pseudocode since, it's difficult to put, everything in one slide so. What I did here was as you can see generate report will put everything into the results map variable. The. Other method convert to format for. The report class is abstract, no implementation, that no complexities, are there in the plus, the. Complexities, are in the report I am PL class which, is where I'm actually implementing. The report. So. Convert. To format now. This one was a very straightforward, method initially, I just had one entry, argument. In there which was HTML, when. It was HTML, I call a method called generate HTML okay. So, that's the easy part for me just, write one method where I just get the data from the result map and place, it into the HTML file, but. Then in future if someone comes with a different form at all they, will have to do is add, another case statement for Excel or a new. Statement, for PDF, and they can write one more method which implements. The PDF or Excel format so. That way you are actually achieving, that, basic, infrastructure, is already in place but, you are giving the flexibility, in, future, to expand, your infrastructure, in whatever, direction your team wants it to go in so. This is one example of, abstraction. Another. One which is not, this very explicit, I, would say on example. Where you actually say abstract, or you call an interface, and so on but. We are all using abstraction, in our regular day-to-day code. Also so. When you are doing logging, you are passing in a bunch of data to your methods username, password, you. Actually, don't care about whether, it is user a which is logged in or user B which is logged in it's, an abstract entity for you guys so, that's how we are actually using artist. Abstraction. In our test cases already but. This is just another example of, using abstraction. So. Just. In a nutshell, what. We are doing or what we are gaining from abstraction, is we, design very simple and then. Provide room later on for the complexities. So. With that we, have our first oops, principle, completed, and move. On to the second one. Encapsulation. This is my favorite, one and I would say one of the most common. Thing. That almost. All of us here use it so, let me quickly go over the definition it, basically refers to bundling, of data along, with the methods that operate on the data and, the three items below are sort of the correct six of it what, you're doing in encapsulation, is putting. Elements inside, a larger, abstract, entity you. Are revealing as little as possible you are basically saying that if you need to know then I will give you the information if not the, information is hidden from you and then. It enforces, correct usage and. The, best example for this is page. Object, model how, many of you use page object model in your frame works okay. Almost most of the room so. Next time you are using a page, object model thing about it as encapsulation, you are already using that particular, utility that, principle in, your automation frameworks, so. As we know page. Object model is pretty straightforward. This. Is one for the login page very, simple, to elements, that we have to interact with the username password and then the third one is the, login button and. To. Interact with those three elements we have a method, login, everything. Happens in that method all the web interact the element, interactions, happen in the login method I just. Want to call out a few things in here the ones highlighted in blue. When. I'm defining, the locators, I want to make them private because, I don't want anyone else to modify them no one should there should be no need for anyone to change those locators, so. That's why they are private and this is a way of we are hiding, the data in encapsulation.

But. The method login it, can be called from any page in our application, and that's, why it's made public so. We, are hiding what we don't want people to change and people I mean other, automation. Engineers, I would say we. Don't want them to modify the locator so they are private and login. Should, be their public available to each and everyone in the team now. Let's take a look at another example, this. Is a list page where it's, a very straightforward page I just have one text field on the page and a, search button, what. It does is whenever I enter a text into the text field and, I click on the submit it gives me a bunch of search, results, very, straightforward. Now, I have written it in a different manner I have. Provided two methods out here for the element. Interactions, one is the set search text and the, submit it. Is pretty straightforward again, I have a common method called perform, search. Where I can, click, uh perform both the action simultaneously. I always. Prefer to have a consolidated, method for all the elements because saying, for example you have a page like. A user data. Entry page where you have ten fields that you need to enter and then, you have to submit when, you are actually implementing, that in your step definition, you, will be actually calling. Ten different methods to avoid that it's better to place, it in your page, object class itself so. That's the consolidated, method I have out here but. The advantage of having a individual, method for, every element is like this say. For example, I want to run a negative test and, I, want to just say I don't enter any data into the text field I just want to click on submit and on, submit I should see an error message showing, up so. That's where that method, on line 20 will come into picture you just click on submit and you are all set also. There are other ways of writing the perform, search method you can put some case statements, in there depending, on how your data comes in you can either enter the value interact with the element or not, interact with the elements so, there are different ways of writing your page object a, couple. Things I want to just call out for page objectives a lot of times people say that why, should I write a whole class when, I just have two elements to interact I could, easily write, those lines of code driver dot find element in my test, implementation. And I'm, all set why do I need a page object for this and the, big reason I give this is like this you. Know your applications, so you know exactly what to call where to call and so on but, anytime you get a new member in your team he's.

You're Looking at other page objects and only for that search page he cannot find the page object where. Exactly is he going to look for that page object or those, elements interact with is he going to write the same driver, dot find element methods in whatever. Test case he is going to implement then. You have the code duplication maintenance. Becomes an issue and, so on so, even though it feels like you are writing ten lines of code for just two fields elements, long, term maintenance will, become much much easier if you do that and as. We most of us know the, biggest drawback, for automation is the maintenance cost, lot, of teams are really hesitant, about with to go into automation because like oh no then we have to keep on maintaining this, it's not like a one-time thing that, we can do so, keeping, your maintenance costs low is one of the goals and that is where the principles are helping us, other. Thing is someone also told me recently that oh instead of having they, we call this page object model as a page repository, on that, page you have all the elements on that page, UI page actually. Mapped but. Instead of that how, can I instead. Of going that way how can I not try, another way where I come, up with a module repository. So. By model repository, what they mean is if they have ten pages in say a user management, module all. The, elements on those ten pages are co-located, in one, huge, file, so. Again I would, not suggest that approach at least in my case I have whenever I have tried to use that approach we ended up with a huge debt, because. User, name on page one and user name on page name page, ten might. Be different and then. Again a new person, or a new team member who inherits, the quote from you it comes in how, is this supposed to know which page which user name belongs to which page so. Maintenance costs grow higher if you move away from this model so. This. Is the best example of encapsulation, and. These. Are the advantages that we gain from using, this page object model we. Are hiding the data which we, don't want people to modify we. Are giving them sort of hooks to look into the public methods those are the ones that they can actually use for their automation, it. Helps, easier maintenance, because. We know whenever something changes on UI page we, know the exact page we have to go and make the change in and the. Code is much much readable you really exactly, know where, to go when, particular. Pages are being, modified so. These are some of the biggest advantages of encapsulation. And. Then we, move on to the third pillar. Or the scientist third item. On the, Fantastic Four list, this. Is inheritance, today. Morning we had a quick violation, from Simon, about genes, DNA, and everything we, all know we inherit our characteristics. From our parents, our looks, our temperament, and all, these details come from our parents, so, the same logic works. With software, also. We. Can actually make sure that, we create. One entity and a. Second entity which is inheriting. It from the parent has the same exact, custom behavior of, the parent, so. Let's, go back to our favorite page object model again now. It's the same example but I have made some changes out here I have, a page, class which, is my base class or the parent class three.

Methods In there one, is for checking, the header one. Is for checking the footer and the, highlighted, one sort of combines both of them and add some assertions, in there so. Ideally. In your application, your footer and header should, be very, consistent, so your footer is same on all the pages I have just put some dummy code out there again, and. You. Are just making sure that you are on the right page and that is what the page object is page loaded method is giving us assertions. In case you're not on the right object right, page the. Assertions will fail your test fails, immediately. So. This is my parent, class then. I wrote my, list class to extend the parent, which. Is page class and all. I did was the east page loaded method that I have in my parent I just, used, it in my child class to. Make sure that when I'm performing a search I'm actually on the right page or not so. This way I have inherited the behavior, or the. Characteristics. Of my parent into my child class it's. Not just this particular class list page if I have ten other page, classes, all, of them I can still use the East page loaded method the only difference is I pass in a different string for every page class that's out there so, if it's a login page I can just pass in a string called is login page if it's, a user, management, page I pass in a different string which is my header and I'm, all set so, my, code. Is basically, coming from the parent class but, I can still use the same method. Across all my page classes, in there so, this is one example of, inheritance. What. Inheritance, actually give us as you could see is I wrote the code one time I used it in, my hundred other pages so we are reusing the same code again and again your, code is centralized, now tomorrow if some developer, comes in and he changes the header text so. That you saw out there instead of my ID he changes it to by name or something else all, I need to do is just come to this page make, the change rest, of my test cases rest of my automation framework is not impacted, at all so. That's one advantage of, inheritance. And. Again, in written sis normally used to keep your file counts, and code, count low because of these attributes. So. This is our third, principle that. We use in other automation, also. Then. Comes polymorphism. This. Is actually the tricky one from my point of view it's a, little. Bit complicated and, I have tried my best to, give some good examples out here, what. Polymorphism, basically means is many, form, poly means many mods means forms so, your software, your code can exist in many forms that's the meaning. Of this and here's. Actual definition, of. The. Condition of occurring in several different forms now how, can this happen in our software how can it change behavior. So. Let's take our example again our page objects, now. I have added a method called logout. Every. Page that on your application will, surely have, a link to logout, of that application, and. In my case I just use a simple. Strategy. By ID find. Out the element logout link and then perform the click action very. Straightforward, every. Page class which inherits this, from the page will. Have this logout method, now. Imagine. A case where the, developers are not following the development, principles they, are not reusing, the code they are not being consistent with their strategies, and they, say I am creating a new page called list page and I'm, going to name, the logout link on then P on that page something different than the rest just. Because I want to so. They come up with the strategy that on the list page the logout link does not have the ID as logout it has, an ID as list logout, link sort. Of hundred pages that I have in my application 99. Of them have, the, same ID as logout but. One page has this difference. So in that case what I need to do is I just need to write the, logout method, override.

It In my list, page class so. That's what I have done if you can see the annotation, out there called override on line 29, the. Overriding, is a way of changing, the behavior the, method, call is the same but, you are still changing how that method particularly behaves, so. What I did was just change the ID and I'm. All set so, anytime in, my test cases when I'm calling say user page dot logout it will, call the parent, class method any. Time I'm using list page dot logout it will call the child, class method so, that way you are overriding the method you are sort of overriding, it to behave, in a different manner so. This is how polymorphism. Works in our automation. Now. Another example of polymorphism is, something called as overloading. So, in this case as you see both the logout methods their signature is the same it's public void logout, with no arguments, so, they are exactly the same the internal behavior is different now. This other case you will see something different out here this. Is a navigate, to page method we, most, of the time we need some methods like this where we have a page name being passed in you, have to click on the page name and you reach, the page that you are interested in it. Can be in form, of tabs or it can be a left hand menu panel that you are clicking on to reach the page you're interested, so. I have a method for that navigate. To page which, takes in the page name now. What happens here is, whenever. This. Means method is called I have click on that link and the, page opens up but. Not all pages might, be that easily accessible, some, of them I have to go through some breadcrumbs, basically, I have to click on two links to, reach the page of interest in, that case what, I could do is for, that sub page I could, actually write. Another method with. The same name but a. Different signature and by signature I mean the number of arguments that I'm passing or the return value of that method can change in, my case I just changed the signature by passing in two arguments, the, main page name and the child page name so, internally. What my code would look like is I click on the page, name first. Then I click on the child page and that, way I reach the page of interest, so. Why would I do that there, is another way of doing it you could just write a completely new method saying navigate, to list page instead of just navigate to page that's, one way of doing it but, sometimes you want to have that consistency for, people to understand, when I say navigate, to page I can, do this action in case, there are three pages. That you have to go through you could again change the signature and, just say pass in the three arguments that you need to reach the page of interest so, that continuity in your coding style that is also something which is helpful for your. Team members because, they know the navigate to page is taking, them to XYZ page, so. That's where polymorphism, comes into picture, there. Are a lot of other examples with polymorphism, that are. Actually, very difficult to sort of present in the next five minutes but. If you want, you can come talk to me and there are a few examples that I can walk you through but. What polymorphism. Gives us is code. Is being reused my, same method log out method or my navigate to page method I'm using it throughout my application, they. Are still flexible enough that they can be modified with, new enhancements that come in today, your page was coming with, one click tomorrow, your page might be coming with two clicks so, you still have that flexibility, to change your methods accordingly, so. This is one way of using polymorphism. In your automation. With. This we actually conclude all the fantastic force, of oops. Since. We have couple more minutes I just want to talk about DRI, principle, this, is something all of us must, be familiar with it's called. It's. Called K is s also, lot. Of different names for this but, this is what I always identify, with don't. Repeat yourself this. Is something really important, in our automation framework not just automation I would say even in application, code when, you are doing code reviews when, you are checking out code written by someone else if you, see a few lines of code which are literally like copy pasted, from somewhere, if you see that you have to say something and do something about it basically that's, what this principle actually talks about. Repeating. Code is actually a big, drawback because again. The cost is maintenance if you, change something somewhere, you have to make find all the search and replace items, and, then make those changes and search and replace is not always that easy as we all know so.

Don't, Repeat yourself is something where if you see some common code you. Need to either move it into a common utility into. A common, page, class or a parent, class and that. Way you can avoid maintenance. Cost and you can get, code reuse out of that and, here, are a few examples that I literally, came up as I was working on this presentation. Navigating. To a particular page it can be part of a common utility or it can be part of your base parent class checking. If a page is loaded or not something. We always need to do as part of our testing. Logging. In logging, out data, retrieval. Validating. File contents, these. Are just a few of the examples there are tons more where. You will need utilities, and those, are sort of the ones which I even talked at the beginning those helper classes that we talked about some. Of these things can end up in those helper classes so. This is a very important, part because, again maintenance. Maintenance maintenance, that. Is something which always brings us down so. If we can avoid that we. Are sort of have 1/2 the war in this. So. With. This I would almost like to conclude my presentation by. Summarizing what, have we achieved till now I, hope. It's not just this I. Hope. You, don't just use the words like encapsulation. And polymorphism, when, you go out you actually remember the examples, that went with it, and. The whole point of these examples, was to achieve these things code. Reuse, flexibility. For code to, enhance in future and low, maintenance cost, so. This, is what I wanted to present in front of you thank. You all for listening to me.

2019-10-26 01:23

Show Video

Comments:

Great overview. Thank you

Very useful. Thanks Aditi Mulay.

Other news